如何避免这个使用 Promises 的 JavaScript 函数中的递归?

问题描述 投票:0回答:1

我有一个 JavaScript 函数,可以生成无休止的幻灯片。该代码有效,但我担心每次迭代都会以递归调用自身结束。使用开发人员工具,调用堆栈似乎正在增长。

我尝试删除对

show_slides
的递归调用,并将函数体包装在
while (true)
循环中,但这会导致浏览器锁定,消耗大量 CPU 并且不显示任何内容。

我想我错过了在某处等待 Promise 解决的机会,并尝试将

.then(() => {})
附加到内部链和外部链,但没有成功。

假设这是使堆栈增长(无限),而不是被视为尾递归并实际作为循环执行,那么我需要什么才能将函数体变成循环?

更新:几次迭代后 Chrome 开发工具调用堆栈的屏幕截图:

Call stack

const duration = 2000; // time (msec) to display each slide
const sizes = [
  [4000, 500],
  [1000, 4000],
  [600, 400],
  [100, 200],
  [4000, 4000]
];
const sleep = ms => new Promise(r => setTimeout(r, ms));
let n = 0;

function show_slides(duration) {
  const my_parent = document.querySelector('#slide-div');
  let size_index = n++ % sizes.length;
  let w = sizes[size_index][0];
  let h = sizes[size_index][1];

  let my_randomizer = `https://placehold.co/${w}x${h}?text=${w}+x+${h}\npx`;
  fetch(my_randomizer)
    .then(my_response => my_response.blob())
    .then(my_blob => {
      let my_url = URL.createObjectURL(my_blob);
      sleep(duration)
        .then(() => {
          URL.revokeObjectURL(my_parent.querySelector('img').src);
          my_parent.querySelector('img').src = my_url;
          // A recursive call to iterate??
          show_slides(duration);
        });
    })
    .catch(my_error => console.error('Error: ', my_error));
}
* {
  box-sizing: border-box;
}

html {
  height: 100%;
  width: 100%;
}

body {
  background-color: #dbd2c3;
  font-family: Arial, Helvetica, sans-serif;
  /* prevent body from displacing */
  margin: 0;
  /* body should perfectly superimpose the html */
  height: 100%;
  width: 100%;
}

.outer-div {
  display: flex;
  flex-flow: column;
  height: 100%;
  /* Now create left/right margins */
  margin: 0 0;
}

.inner-fixed-div {
  margin-top: 0.5em;
}

.inner-remaining-div {
  margin-bottom: 0;
  flex-grow: 1;
  /* hints the contents to not overflow */
  overflow: hidden;
}

.picture-div {
  /* force the div to fill the available space */
  width: 100%;
  height: 100%;
  /* center child elements */
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.picture-div-img {
  max-width: calc(100% - 0.5em);
  /* included 0.5em * 2 margin on parent */
  max-height: calc(100% - 0.5em);
  /* included 2em margin on parent, may need adjust this further */
  border: 2px solid black;
}
<!DOCTYPE html>
<html lang="en">
<!-- Self-contained slideshow demo -->

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

</head>

<body onload="show_slides(duration);">
  <div class="outer-div">
    <div class="inner-fixed-div">
      <h1>Lorem Ipsum</h1>
    </div>
    <div class="inner-remaining-div">
      <div id="slide-div" class="picture-div">
        <!-- Placeholder <img> element for slides -->
        <img class="picture-div-img">
      </div>
    </div>
  </div>
</body>

</html>

javascript html fetch-api es6-promise tail-recursion
1个回答
0
投票

参见 为什么 setTimeout() 在 Chrome DevTools 下使我的调用堆栈变得混乱? – Chrome 开发者工具会自动捕获异步“堆栈跟踪”以进行调试。您的代码是正确的,并且调用堆栈实际上并未增长。 (我不确定开发人员工具如何处理无限增长,但它们可能会在某个地方截断;堆栈溢出将是开发人员工具中的一个错误。)

不过,只要您可以依赖对 async/await 的支持,您就可以获得更干净的调试体验和更干净的代码:

const duration = 2000; // time (msec) to display each slide
const sizes = [
  [4000, 500],
  [1000, 4000],
  [600, 400],
  [100, 200],
  [4000, 4000],
];
const sleep = ms => new Promise(r => setTimeout(r, ms));

async function show_slides(duration) {
  const my_img = document.querySelector('#slide-div img');
  let n = 0;

  while (true) {
    const size_index = n++ % sizes.length;
    const [w, h] = sizes[size_index];
  
    let my_randomizer = `https://placehold.co/${w}x${h}?text=${w}+x+${h}\npx`;
    try {
      const my_response = await fetch(my_randomizer);
      const my_blob = await my_response.blob();
      const my_url = URL.createObjectURL(my_blob);
      my_img.src = my_url;
    } catch (my_error) {
      console.error('Error: ', my_error);
      break;  // or continue after a delay
    }
    
    await sleep(duration);
  }
}
* {
  box-sizing: border-box;
}

html {
  height: 100%;
  width: 100%;
}

body {
  background-color: #dbd2c3;
  font-family: Arial, Helvetica, sans-serif;
  /* prevent body from displacing */
  margin: 0;
  /* body should perfectly superimpose the html */
  height: 100%;
  width: 100%;
}

.outer-div {
  display: flex;
  flex-flow: column;
  height: 100%;
  /* Now create left/right margins */
  margin: 0 0;
}

.inner-fixed-div {
  margin-top: 0.5em;
}

.inner-remaining-div {
  margin-bottom: 0;
  flex-grow: 1;
  /* hints the contents to not overflow */
  overflow: hidden;
}

.picture-div {
  /* force the div to fill the available space */
  width: 100%;
  height: 100%;
  /* center child elements */
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.picture-div-img {
  max-width: calc(100% - 0.5em);
  /* included 0.5em * 2 margin on parent */
  max-height: calc(100% - 0.5em);
  /* included 2em margin on parent, may need adjust this further */
  border: 2px solid black;
}
<!DOCTYPE html>
<html lang="en">
<!-- Self-contained slideshow demo -->

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

</head>

<body onload="show_slides(duration);">
  <div class="outer-div">
    <div class="inner-fixed-div">
      <h1>Lorem Ipsum</h1>
    </div>
    <div class="inner-remaining-div">
      <div id="slide-div" class="picture-div">
        <!-- Placeholder <img> element for slides -->
        <img class="picture-div-img">
      </div>
    </div>
  </div>
</body>

</html>

© www.soinside.com 2019 - 2024. All rights reserved.