小编典典

JavaScript ES6 承诺 for 循环

all

for (let i = 0; i < 10; i++) {
    const promise = new Promise((resolve, reject) => {
        const timeout = Math.random() * 1000;
        setTimeout(() => {
            console.log(i);
        }, timeout);
    });

    // TODO: Chain this promise to the previous one (maybe without having it running?)
}

以上将给出以下随机输出:

6
9
4
8
5
1
7
2
3
0

任务很简单:确保每个 Promise 仅在另一个 Promise 之后运行(.then())。

由于某种原因,我找不到办法做到这一点。

我尝试过生成yield函数

使用async我会简单地使用async.series().

你如何解决它?


阅读 100

收藏
2022-07-18

共1个答案

小编典典

正如您在问题中已经暗示的那样,您的代码会同步创建所有承诺。相反,它们应该只在前一个解决时创建。

其次,每个创建的 Promise 都new Promise需要通过调用resolve(or reject)
来解决。这应该在计时器到期时完成。这将触发then您对该承诺的任何回调。为了实现链,这样的then回调(或)是必要的。await

有了这些成分,有几种方法可以执行这种异步链接:

  1. 循环以for立即解决的承诺开始

  2. Array#reduce立即解决的承诺开始

  3. 使用将自身作为解析回调传递的函数

  4. 使用 ECMAScript2017 的async/await语法

  5. 使用 ECMAScript2020 的for await...of语法

但让我先介绍一个非常有用的通用函数。

许诺setTimeout

使用setTimeout很好,但我们实际上需要一个在计时器到期时解决的承诺。所以让我们创建这样一个函数:这被称为 promisifying
一个函数,在这种情况下我们将 promisify setTimeout。它将提高代码的可读性,并可用于上述所有选项:

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

请参阅以下每个选项的片段和评论。

1. 有for

可以 使用for循环,但必须确保它不会同步创建所有 Promise。相反,您创建一个初始的立即解决承诺,然后在之前的承诺解决时链接新的承诺:

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

for (let i = 0, p = Promise.resolve(); i < 10; i++) {
    p = p.then(() => delay(Math.random() * 1000))
         .then(() => console.log(i));
}

所以这段代码创建了一长串then调用。该变量p仅用于不丢失该链的跟踪,并允许循环的下一次迭代在同一链上继续。回调将在同步循环完成后开始执行。

重要的是then-callback 返回 创建的承诺delay():这将确保异步链接。

2. 有reduce

这只是对先前策略的一种更实用的方法。您创建一个与要执行的链长度相同的数组,并从立即解决的承诺开始:

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

[...Array(10)].reduce( (p, _, i) => 
    p.then(() => delay(Math.random() * 1000))
     .then(() => console.log(i))
, Promise.resolve() );

当您实际上有 一个包含要在 Promise 中使用的数据的数组时,这可能更有用。

3. 使用一个函数将自身作为解析回调传递

这里我们创建一个函数并立即调用它。它同步创建第一个承诺。当它解析时,再次调用该函数:

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

(function loop(i) {
    if (i >= 10) return; // all done
    delay(Math.random() * 1000).then(() => {
        console.log(i);
        loop(i+1);
    });
})(0);

这将创建一个名为 的函数loop,在代码的最后,您可以看到它立即使用参数 0 调用。这是计数器和 i 参数。如果该计数器仍低于
10,该函数将创建一个新的 Promise,否则链接将停止。

解决后delay(),它将触发then回调,该回调将再次调用该函数。

4. 有async/await

现代 JS
引擎支持这种语法

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

(async function loop() {
    for (let i = 0; i < 10; i++) {
        await delay(Math.random() * 1000);
        console.log(i);
    }
})();

这可能看起来很奇怪,因为promise 似乎 是同步创建的,但实际上async函数在执行 first 时 返回await。每次等待的
promise 解决时,函数的运行上下文都会恢复,并在 之后继续await,直到遇到下一个,所以它继续直到循环结束。

5. 有for await...of

借助 EcmaScript 2020,它for await...of找到了通往现代 JavaScript
引擎的道路。尽管在这种情况下它并没有真正减少代码,但它允许将随机区间链的定义与它的实际迭代隔离开来:

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

async function * randomDelays(count, max) {
    for (let i = 0; i < count; i++) yield delay(Math.random() * max).then(() => i);
}

(async function loop() {
    for await (let i of randomDelays(10, 1000)) console.log(i);
})();
2022-07-18