在所有承诺均得到解决的假设下,异步迭代(for-await-of
循环)比使用Promise.all
更快吗?
来自specification on asynchronous iteration:
每次访问序列中的下一个值时,我们都会隐式
await
从迭代器方法返回的承诺。
使用异步迭代:
let pages = [fetch('/echo/json/'), fetch('/echo/html/'), fetch('/echo/xml/')]
for await (let page of pages) {
console.log(page)
}
使用Promise.all
:
let pages = await Promise.all([fetch('/echo/json/'), fetch('/echo/html/'), fetch('/echo/xml/')])
pages.forEach(page => console.log(page))
它们两者都是并行获取页面的,但是我想知道异步迭代是否在所有页面完成获取之前开始循环。我曾尝试在浏览器的devtools中限制网络,以模拟这种情况,但任何差异仍然很少被注意到。
答案 0 :(得分:4)
异步迭代(for-await-of循环)比使用Promise.all快吗?
不。当最后一个承诺解决时,循环和Promise.all都将完成,这几乎是同时发生的。如果最后要解决的承诺是数组中的第一个承诺,则Promise.all会立即完成,而循环仍必须迭代其他元素,这可能会导致少量开销,但这无关紧要。唯一真正重要的情况是:
如果其中一个承诺被拒绝,Promise.all会立即退出,而循环必须达到该承诺。
我想知道异步迭代是否在所有页面完成提取之前就开始循环。
是的,您可以更早地获得第一个结果,但是所有结果将同时可用。如果您想向用户显示数据,则使用for循环是有意义的,因为他可以在其余数据仍在获取的同时开始读取数据;但是,如果您想累积数据,则等待它们都是有意义的,因为这简化了迭代逻辑。
答案 1 :(得分:0)
我认为您不需要await
,这也不是获取数据并将其写入控制台的最快方法:
let pages = Promise.all([fetch('/echo/json/'), fetch('/echo/html/'), fetch('/echo/xml/')])
pages.then((page) => {console.log(page)});
因为pages.then()
将等待每个诺言的兑现。
但是您可以像上面那样异步获取数据。并将它们写入控制台,而无需等待页面。像这样:
var sequence = Promise.resolve();
['/echo/json/','/echo/html/','/echo/xml/'].forEach(function(url) {
sequence.then(function() {
return fetch(url);
})
.then((data) => {console.log(data)});
});
但是上面的代码没有考虑页面顺序。如果页面顺序是您的事。 您可以尝试一下,这是获取数据并按顺序显示它们的最快方法:
var sequence = Promise.resolve();
// .map executes all of the network requests immediately.
var arrayOfExecutingPromises =
['/echo/json/','/echo/html/','/echo/xml/'].map(function(url) {
return fetch(url);
});
arrayOfExecutingPromises.forEach(function (request) {
// Loop through the pending requests that were returned by .map (and are in order) and
// turn them into a sequence.
// request is a fetch() that's currently executing.
sequence = sequence.then(function() {
return request.then((page) => {console.log('page')});
});
});
答案 2 :(得分:0)
问题的一部分是,如果你在一个 Promise 数组上使用 for-await-of,你会按照指定的顺序迭代它,如果给定数组中的下一个 Promise 在前一个之前解决,这无关紧要:
const sleep = time => new Promise(resolve => setTimeout(resolve, time));
(async function () {
const arr = [
sleep(2000).then(() => 'a'),
'x',
sleep(1000).then(() => 'b'),
'y',
sleep(3000).then(() => 'c'),
'z',
];
for await (const item of arr) {
console.log(item);
}
}());
输出:
➜ firstcomefirstserved git:(main) node examples/for-await-simple.js
a
x
b
y
c
z
但有时 - 就像在您的问题中一样,您希望在承诺产生结果后立即处理结果。因此,我决定编写一个异步迭代器,使 for await
以首先承诺先解决的方式工作。代码如下:
// promises is an Array not an Iterable
async function* auxIterator(promises) {
let wrappedPromises = promises.map((p, i) => {
return new Promise((resolve, reject) => {
p.then(r => resolve([r,i]))
.catch(e => reject(e))
})
});
let [r, i] = await Promise.race(wrappedPromises);
yield r;
promises.splice(i,1);
if (promises.length)
yield * auxIterator(promises)
}
async function * frstcmfrstsvd(promises) {
let wrappedPromises = promises.map((p, i) => {
return new Promise((resolve, reject) => {
Promise.resolve(p).then(r => resolve({value: r, index: i, status: 'fulfilled'}))
.catch(e => resolve({reason: e, index: i, status: 'rejected'}))
})
});
yield * await auxIterator(wrappedPromises);
}
export default frstcmfrstsvd
您可以在 npm 包 frstcmfrstsvd 中找到完整代码。
尚未进行详尽的测试,但乍一看,Promise.allSettled 的性能似乎要好一些:
> node examples/performance-reject-frstcmfrstsvd.mjs
frstcmfrstsvd: 323.104ms
allsettled: 317.319ms
> node examples/performance-reject-frstcmfrstsvd.mjs
frstcmfrstsvd: 327.142ms
allsettled: 315.415ms
> node examples/performance-reject-frstcmfrstsvd.mjs
frstcmfrstsvd: 322.753ms
allsettled: 318.955ms
> node examples/performance-reject-frstcmfrstsvd.mjs
frstcmfrstsvd: 325.562ms
allsettled: 317.375ms
> node examples/performance-reject-frstcmfrstsvd.mjs
frstcmfrstsvd: 322.25ms
allsettled: 318.09ms
查看文件 examples/performance-reject-frstcmfrstsvd.mjs
因此,所有这些实验的结论似乎是,正如@jonas-wilms 所说,它们大致同时完成