代码之家  ›  专栏  ›  技术社区  ›  Toby Harnish

为什么依次减少对承诺的等待,而forEach则没有?

  •  0
  • Toby Harnish  · 技术社区  · 1 年前

    考虑代码:

    const promises = [
      new Promise(r => setTimeout(r, 10000, "Hi")),
      new Promise(r => setTimeout(r, 500, "second")),
      new Promise(r => setTimeout(r, 100, "third"))
    ];
    promises.reduce(async(a, promise) => {
      await a;
      const p = await promise;
      console.log(p);
    }, Promise.resolve());

    尽管这三个承诺之间存在巨大的时间差,但这段代码的输出是Hi,first,third。 我的想法:

    在每次迭代中,reduce回调都会立即返回一个挂起的promise,该promise会同步解析。因此,它随后的异步操作将等待回调返回的三个promise得到解决。但我仍然不认为输出应该是这样的,因为这三个承诺是独立的——每个承诺都创建了自己的异步操作链。当执行第一个回调时,函数会立即解析为undefined。然后,异步代码(promise的等待和three的日志记录被推迟到下一个事件周期——放入微任务队列中)。我认为这发生在reduce迭代的所有三个承诺上。

    然而,在forEach循环中,即使使用以下代码,承诺也会被不连续地解决:

    const promises = [
      new Promise(r => setTimeout(r, 10000, "Hi")),
      new Promise(r => setTimeout(r, 500, "second")),
      new Promise(r => setTimeout(r, 100, "third"))
    ];
    promises.forEach(async promise => {
      // try to force the delay of the "promise" in the promises array.
      await 3;
      const p = await promise;
      console.log(p);
    });

    输出是第二、第三、第一。为什么会发生这种情况?

    那么为什么会发生这种情况呢?我完全预料到了一些不同的东西

    1 回复  |  直到 1 年前
        1
  •  4
  •   Nicholas Tower    1 年前

    简单的答案是,在forEach的情况下,你要制作3个短的承诺链,而在reducer的情况下你要制作1个长的复杂链。3条短链分别只等待它们最初的承诺,但长链通过代码 await a ,正在等待前一次迭代完成后再继续。

    现在来谈谈细节。

    const promises = [
      new Promise(r => setTimeout(r, 10000, "Hi")),
      new Promise(r => setTimeout(r, 500, "second")),
      new Promise(r => setTimeout(r, 100, "third"))
    ];
    

    首先,我要指出,这几行代码开始了超时 立即 。因此,无论以后发生什么,计时器都将始终在100毫秒、500毫秒和10000毫秒时关闭(加上或减去计时器通常的不精确性)。这个 await 让我们决定你想暂停做什么,但它们不会影响最初的3个承诺何时解决。

    考虑到这一点,让我们看看foreach的情况,因为它是最简单的。我会把它扔掉 await 3 因为这几乎没有什么作用。

    promises.forEach(async promise => {
      const p = await promise;
      console.log(p);
    });
    

    您的函数首先被调用10000ms promise。您等待10000毫秒的承诺,这会导致您的函数返回自己的承诺。 .forEach 不在乎返回什么,所以它会移动到数组中的下一个元素,即500ms promise。您等待500毫秒的承诺并返回。100毫秒的承诺也是如此。

    100毫秒后,最初的承诺就解决了。这意味着您的函数的第三个实例现在可以恢复,第三个可以注销。在500ms标记处,原始承诺解析,允许恢复函数的第二个实例。最后,在10000ms标记处,该函数的第一个实例可以恢复。


    现在,让我们把注意力转向减速器案例。

    promises.reduce(async (a, promise) => {
      await a;
      const p = await promise;
      console.log(p);
    }, Promise.resolve());
    

    在第一次迭代中, a 是来自的承诺 Promise.resolve() promise 是10000米的承诺。你在等 ,导致您的函数返回新的promise。Reduce并不关注承诺,只是盲目地将其传递给下一次迭代。

    现在我们进入第二次迭代 是第一次迭代返回的承诺,以及 许诺 是500毫秒的承诺。第二次迭代正在等待 自从 是第一次迭代的承诺,这建立了一个链,第二个函数必须等待第一个函数解析其承诺。第二次迭代返回一个promise,并将其传递给第三次迭代,第三次重复执行类似的等待。

    现在我们已经完成了同步代码,一个微任务进入队列,第一个函数在 等待 。第一个函数移到下一行 等候 s 许诺 ,这就是10000毫秒的承诺。此时函数将暂停执行。

    过了一会儿,100毫秒的承诺就实现了。没有什么直接等待它,所以在这一点上没有发生任何事情。稍后,500毫秒的承诺得以实现。同样,没有什么直接等待它。甚至在稍后,10000米的承诺也会实现。这允许恢复第一个功能。它记录“嗨”,然后返回,从而解决了自己的承诺。因为它解决了它的承诺,函数2(它耐心地坐在上面 等待 )现在可以恢复。然后函数2等待 许诺 ,这是500毫秒的承诺。这个承诺已经解决了,所以在最短的中断之后,它会恢复,记录“秒”并返回。这反过来解锁等待的功能3 许诺 (100毫秒),然后继续。它注销“第三个”并完成。