Promise.all 和 for-await-of 的性能表现

8
在所有承诺都被解决的前提下,异步迭代(for-await-of循环)是否比使用Promise.all更快?根据异步迭代规范

每次访问序列中的下一个值时,我们都会隐式地等待迭代器方法返回的承诺。

使用异步迭代:

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))

两者都可以并行抓取页面,但我想知道异步迭代是否在所有页面抓取完成之前开始循环。我已经尝试通过浏览器开发工具来限制网络速度以模拟这种情况,但仍然无法感知到任何差异。

3个回答

8
异步迭代(for-await-of 循环)比使用 Promise.all 更快吗?
不是。当最后一个 promise 解决时,循环和 Promise.all 都会完成,这将大致同时发生。如果解决的最后一个 promise 是数组中的第一个 promise,则 Promise.all 立即完成,而循环仍然必须迭代其他可能导致轻微开销的元素,但这应该无关紧要。唯一真正重要的情况是:
如果其中一个 promise 被拒绝,Promise.all 就会立即退出,而循环必须到达该 promise。
我想知道异步迭代在所有页面都完成获取之前是否开始循环。
是的,您可以提前获取第一个结果,但所有结果将同时可用。如果您想向用户显示数据,则使用 for 循环是有意义的,因为他可以在其余部分仍在获取数据时开始阅读,但是如果您想累积数据,则等待它们都有意义,因为它简化了迭代逻辑。

0
我认为你不需要使用await,而且这也不是将数据从控制台获取并写入的最快方式。
let pages = Promise.all([fetch('/echo/json/'), fetch('/echo/html/'), fetch('/echo/xml/')])
pages.then((page) => {console.log(page)});

pages.then() 会等待每个 Promise 完成。

但是你可以像上面那样异步获取数据。并在 pages 之前不必等待将它们写入控制台。就像这样:

 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')});
    });
  });

0
问题的一部分是,如果您在一组 Promise 上使用 for-await-of,无论给定数组中的下一个 Promise 是否在前一个 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 按照先解决的承诺先被服务的方式工作。以下是代码:

async function* frstcmfrstsvd(promises) {
  let resolver = []

  // create an array sortedByFulfillment of pending promises and make available their resolvers in the resolver array
  let sortedByFulfillment = []
  for (let i = 0; i < promises.length; i++) {
    sortedByFulfillment.push(new Promise((res, rej) => {
      resolver.push(res)
    }))
  }

  promises.forEach((p, i) => {
    Promise.resolve(p).then(r => { 
      // resolve the first pending promise on the sortedByFulfillment array 
      let res = resolver.shift()
      res({ value: r, index: i, status: 'fulfilled' })
    }, err => {
      let res = resolver.shift()
      res({ reason: err, index: i, status: 'rejected' })
    })
  })

  for await (let result of sortedByFulfillment) {
    yield result
  }
}

export default frstcmfrstsvd

您可以在npm包frstcmfrstsvd中找到完整的代码。

目前还没有详尽的测试,但初步看来,Promise.allSettled的性能似乎稍微好一些:

  node examples/performance-reject-frstcmfrstsvd.mjs
frstcmfrstsvd: 308.914ms
allsettled: 309.254ms
  node examples/performance-reject-frstcmfrstsvd.mjs
frstcmfrstsvd: 306.977ms
allsettled: 309.917ms
  node examples/performance-reject-frstcmfrstsvd.mjs
frstcmfrstsvd: 308.531ms
allsettled: 303.636ms
  node examples/performance-reject-frstcmfrstsvd.mjs
frstcmfrstsvd: 310.559ms
allsettled: 307.805ms
  node examples/performance-reject-frstcmfrstsvd.mjs
frstcmfrstsvd: 309.94ms
allsettled: 308.318ms

查看文件 examples/performance-reject-frstcmfrstsvd.mjs

因此,这个实验的结论似乎是,就像@jonas-wilms所说的那样,它们两者基本上在同一时间完成了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接