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
任务很简单:确保每个诺言仅在另一个诺言(.then())之后运行。
.then()
由于某种原因,我找不到办法。
我尝试了生成器函数(yield),尝试了返回promise的简单函数,但总的来说,它总是归结为同一个问题: 循环是同步的 。
yield
使用异步,我只使用async.series()。
async.series()
您如何解决?
正如您已经在问题中暗示的那样,您的代码同步创建了所有promise。相反,仅应在上一个解析时创建它们。
其次,new Promise需要使用调用resolve(或reject)来解决创建的每个promise。计时器到期时应执行此操作。这将触发then您对该承诺的任何回调。为了实现链,这种then回调(或await)是必需的。
new Promise
resolve
reject
then
await
有了这些成分,有几种方法可以执行此异步链接:
以for立即解决承诺开始的循环
for
从此Array#reduce开始,立即解决承诺
Array#reduce
具有将自身作为分辨率回调传递的函数
使用ECMAScript2017的async/ await语法
async
使用建议的ECMAScript2020的for await...of语法
for await...of
请在下面查看每个选项的摘要和注释。
您 可以 使用for循环,但是必须确保它不会newPromise同步执行。相反,您可以创建一个初始的立即解决的Promise,然后在先前的Promise解析时链接新的Promise:
newPromise
for (let i = 0, p = Promise.resolve(); i < 10; i++) { p = p.then(_ => new Promise(resolve => setTimeout(function () { console.log(i); resolve(); }, Math.random() * 1000) )); }
reduce
这只是先前策略的一种更实用的方法。您创建一个长度与要执行的链相同的数组,并以立即解决的承诺开始:
[...Array(10)].reduce( (p, _, i) => p.then(_ => new Promise(resolve => setTimeout(function () { console.log(i); resolve(); }, Math.random() * 1000) )) , Promise.resolve() );
当您实际上有 一个包含要在诺言中使用的数据的数组时,这可能会更有用。
(function loop(i) { if (i < 10) new Promise((resolve, reject) => { setTimeout( () => { console.log(i); resolve(); }, Math.random() * 1000); }).then(loop.bind(null, i+1)); })(0);
这将创建一个名为的函数loop,在代码的最后,您可以看到它立即被参数0 调用。这是计数器,还有 i 参数。如果该计数器仍低于10,该函数将创建一个新的Promise,否则链接将停止。的调用resolve()将触发then回调,该回调将再次调用该函数。loop.bind(null, i+1)只是一种不同的表达方式_ => loop(i+1)。
loop
resolve()
loop.bind(null, i+1)
_ => loop(i+1)
现代JS引擎支持以下语法:
(async function loop() { for (let i = 0; i < 10; i++) { await new Promise(resolve => setTimeout(resolve, Math.random() * 1000)); console.log(i); } })();
看起来 好像是new Promise()调用是同步执行的,这看起来可能很奇怪,但实际上,该async函数在执行first时返回await。每次等待的promise解析后,该函数的运行上下文都将还原,并在之后执行await,直到遇到下一个,然后继续进行直到循环结束。
new Promise()
由于根据超时返回承诺是很平常的事情,因此您可以创建一个单独的函数来生成此类承诺。在这种情况下,这称为“ 赋 函数” setTimeout。它可以提高代码的可读性:
setTimeout
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); } })();
甚至在最近,该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); })();