等待 VS Promise.all
for await of VS Promise.all
这有什么区别吗:
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
}
我知道在第一个片段中,所有的 promise 都被同时触发,但我不确定第二个片段。 for 循环是否等待第一次迭代完成以调用下一个 promise ?或者所有的 promise 都是同时触发的,循环内部就像是它们的回调?
如您所说,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
语法确实会一次性触发所有承诺。
一小段代码证明了这一点:
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
但是循环内部没有动作"as a callback"。如果我反转数组,所有日志都会立即出现。我想承诺会立即触发,运行时只是等待第一个解决以进入下一次迭代。
编辑:实际上,根据@Bergi 在他的回答中,当我们将它与异步迭代器以外的其他东西一起使用时,使用 for await
是不好的做法,最好是使用 Promise.all
。
是的,它们绝对不同。 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.all
是处理promises数组的唯一可行选择:
for (const res of await Promise.all(promises)) …
详情见 and 。
当在异步迭代器上当前迭代的计算取决于之前的一些迭代时,就需要 for await ...
。如果没有依赖,Promise.all
是你的选择。 for await
构造旨在与异步迭代器一起使用,尽管 - 如您的示例所示,您可以将它与承诺数组一起使用。
请参阅示例 paginated data in the book javascript.info,了解使用无法使用 Promise.all
重写的异步迭代器的示例:
(async () => {
for await (const commit of fetchCommits('javascript-tutorial/en.javascript.info')) {
console.log(commit.author.login);
}
})();
此处 fetchCommits
异步迭代器向 fetch
请求 GitHub 回购的提交。 fetch
以 JSON 的 30 次提交作为响应,并且还向 Link
header 中的下一页提供 link。 因此下一次迭代只能在上一次迭代有下一次请求的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;
}
}
}
这有什么区别吗:
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
}
我知道在第一个片段中,所有的 promise 都被同时触发,但我不确定第二个片段。 for 循环是否等待第一次迭代完成以调用下一个 promise ?或者所有的 promise 都是同时触发的,循环内部就像是它们的回调?
如您所说,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
语法确实会一次性触发所有承诺。
一小段代码证明了这一点:
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
但是循环内部没有动作"as a callback"。如果我反转数组,所有日志都会立即出现。我想承诺会立即触发,运行时只是等待第一个解决以进入下一次迭代。
编辑:实际上,根据@Bergi 在他的回答中,当我们将它与异步迭代器以外的其他东西一起使用时,使用 for await
是不好的做法,最好是使用 Promise.all
。
是的,它们绝对不同。 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.all
是处理promises数组的唯一可行选择:
for (const res of await Promise.all(promises)) …
详情见
当在异步迭代器上当前迭代的计算取决于之前的一些迭代时,就需要 for await ...
。如果没有依赖,Promise.all
是你的选择。 for await
构造旨在与异步迭代器一起使用,尽管 - 如您的示例所示,您可以将它与承诺数组一起使用。
请参阅示例 paginated data in the book javascript.info,了解使用无法使用 Promise.all
重写的异步迭代器的示例:
(async () => {
for await (const commit of fetchCommits('javascript-tutorial/en.javascript.info')) {
console.log(commit.author.login);
}
})();
此处 fetchCommits
异步迭代器向 fetch
请求 GitHub 回购的提交。 fetch
以 JSON 的 30 次提交作为响应,并且还向 Link
header 中的下一页提供 link。 因此下一次迭代只能在上一次迭代有下一次请求的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;
}
}
}