等待 VS Promise.all
Posted
技术标签:
【中文标题】等待 VS Promise.all【英文标题】:for await of VS Promise.all 【发布时间】:2020-04-28 20:23:04 【问题描述】:这有什么区别:
const promises = await Promise.all(items.map(e => somethingAsync(e)));
for (const res of promises)
// do some calculations
还有这个?
for await (const res of items.map(e => somethingAsync(e)))
// do some calculations
我知道在第一个 sn-p 中,所有的 Promise 都会同时触发,但我不确定第二个。 for 循环是否等待第一次迭代完成以调用下一个 promise ?还是所有的 Promise 都是同时触发的,而循环内部对它们来说就像一个回调?
【问题讨论】:
【参考方案1】:正如您所说,Promise.all
将一次性发送所有请求,然后在所有请求完成后您将收到响应。
在第二种情况下,您将一次性发送请求,但会逐一收到响应。
请参阅这个小示例以供参考。
let i = 1;
function somethingAsync(time)
console.log("fired");
return delay(time).then(() => Promise.resolve(i++));
const items = [1000, 2000, 3000, 4000];
function delay(time)
return new Promise((resolve) =>
setTimeout(resolve, time)
);
(async() =>
console.time("first way");
const promises = await Promise.all(items.map(e => somethingAsync(e)));
for (const res of promises)
console.log(res);
console.timeEnd("first way");
i=1; //reset counter
console.time("second way");
for await (const res of items.map(e => somethingAsync(e)))
// do some calculations
console.log(res);
console.timeEnd("second way");
)();
你也可以在这里试试 - https://repl.it/repls/SuddenUselessAnalyst
希望这会有所帮助。
【讨论】:
事实上你证明了我的第二点。使用for await
实际上会同时触发所有的promise,因此循环的执行时间:4 秒。如果它等待一个完成再进行下一步,总执行时间将是 1+2+3+4 = 10 秒。
确实如此。添加时间部分来证明这一点。【参考方案2】:
实际上,使用for await
语法确实会立即触发所有承诺。
一小段代码证明了这一点:
const sleep = s =>
return new Promise(resolve =>
setTimeout(resolve, s * 1000);
);
const somethingAsync = async t =>
await sleep(t);
return t;
(async () =>
const items = [1, 2, 3, 4];
const now = Date.now();
for await (const res of items.map(e => somethingAsync(e)))
console.log(res);
console.log("time: ", (Date.now() - now) / 1000);
)();
标准输出:
time: 4.001
但循环的内部并不充当“回调”。如果我反转数组,所有日志都会立即出现。我想承诺会立即触发,运行时只是等待第一个解决方案进入下一次迭代。
编辑:实际上,当我们将for await
与异步迭代器以外的东西一起使用时,使用Promise.all
是不好的做法,最好是使用Promise.all
,根据@Bergi 在他的回答中。
【讨论】:
【参考方案3】:是的,它们完全不同。 for await
应该与异步迭代器一起使用,而不是与预先存在的承诺数组一起使用。
只是为了说清楚,
for await (const res of items.map(e => somethingAsync(e))) …
工作原理与
相同const promises = items.map(e => somethingAsync(e));
for await (const res of promises) …
或
const promises = [somethingAsync(items[0]), somethingAsync(items[1]), …];
for await (const res of promises) …
somethingAsync
呼叫立即发生,一次发生,在等待任何事情之前。然后,他们一个接一个被await
ed,如果其中任何一个被拒绝,这绝对是一个问题:它会导致一个未处理的promise拒绝错误。 使用Promise.all
是处理一系列承诺的唯一可行选择:
for (const res of await Promise.all(promises)) …
详情请参阅Waiting for more than one concurrent await operation 和Any difference between await Promise.all() and multiple await?。
【讨论】:
IMO 一组 promise 被同化为异步迭代器,因为它们将控制权交还给事件循环,对吧?如果在我的somethingAsync
函数中,我正确地捕捉到了所有内容怎么办?
是的,数组提供了一个异步迭代器,这就是循环不会引发协议错误的原因。但是该迭代一次只等待一个 promise,忽略了数组后面的 promise 的错误。当然,如果somethingAsync
从不出错,就不会发生任何坏事,但这很难保证。在一系列 Promise 上使用 for await
仍然是一种不好的做法。
好的,将使用Promise.all
,感谢您的澄清【参考方案4】:
当在异步迭代器上当前迭代的计算依赖于之前的一些迭代时,就需要for await ...
。如果没有依赖关系,Promise.all
是您的选择。 for await
构造被设计为与异步迭代器一起使用,尽管 - 在您的示例中,您可以将它与一组承诺一起使用。
请参阅javascript.info 一书中的示例paginated data,了解使用无法使用Promise.all
重写的异步迭代器的示例:
(async () =>
for await (const commit of fetchCommits('javascript-tutorial/en.javascript.info'))
console.log(commit.author.login);
)();
在这里,fetchCommits
异步迭代器向fetch
发出请求,提交 GitHub 存储库。 fetch
以 30 次提交的 JSON 响应,并在 Link
标头中提供指向下一页的链接。 因此下一次迭代只能在上一次迭代有下一个请求的链接后开始
async function* fetchCommits(repo)
let url = `https://api.github.com/repos/$repo/commits`;
while (url)
const response = await fetch(url,
headers: 'User-Agent': 'Our script',
);
const body = await response.json(); // (array of commits
// The URL of the next page is in the headers, extract it using a regexp
let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/);
nextPage = nextPage?.[1];
url = nextPage;
for(let commit of body) // yield commits one by one, until the page ends
yield commit;
【讨论】:
非常清楚,谢谢。在您的代码示例中,for await
的行为类似于yield*
,不是吗?因为每次迭代都会产生多个项目
没有生成器组合,每次提交只有一个yield
。每 30 次迭代后加载一个新页面,接下来的 30 次提交是yield
ed。真的迭代之间的依赖关系每三十次才发生一次。以上是关于等待 VS Promise.all的主要内容,如果未能解决你的问题,请参考以下文章