【问题标题】:Performance of Promise.all and for-await-ofPromise.all 和 for-await-of 的表现
【发布时间】:2018-08-19 10:15:10
【问题描述】:

在所有承诺都解决的假设下,异步迭代(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 中限制网络来模拟这一点,但任何差异仍然太小而无法注意到。

【问题讨论】:

    标签: javascript asynchronous promise async-await


    【解决方案1】:

    异步迭代(for-await-of 循​​环)是否比使用 Promise.all 更快?

    没有。当最后一个 Promise 解决时,循环和 Promise.all 都将完成,这将大致在同一时间。如果最后一个解决的 promise 是数组中的第一个 promise,那么 Promise.all 会立即完成,而循环仍然必须迭代其他元素,这可能会导致少量开销,但这无关紧要。唯一真正重要的情况是:

    如果其中一个承诺被拒绝,Promise.all 将立即退出,而循环必须达到该承诺。

    我想知道异步迭代是否在所有页面完成获取之前开始循环。

    是的,您可以早一点获得第一个结果,但所有结果将同时可用。如果您想向用户显示数据,则使用 for 循环是有意义的,因为他可以在其余部分仍在获取时开始阅读,但是如果您想累积数据,则等待所有数据是有意义的,因为它简化了迭代逻辑。

    【讨论】:

      【解决方案2】:

      我认为你不需要await 这也不是获取数据并将数据写入控制台的最快方法:

      let pages = Promise.all([fetch('/echo/json/'), fetch('/echo/html/'), fetch('/echo/xml/')])
      pages.then((page) => {console.log(page)});
      

      因为pages.then() 会等待每个promise 被解决。

      但是您可以像上面那样异步获取数据。并将它们写入控制台而无需等待页面。像这样:

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

      【讨论】:

        【解决方案3】:

        问题的一部分是,如果你在一个 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首先承诺解决是首先被服务的方式工作。代码如下:

        // 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 所说,它们大致同时完成

        【讨论】:

          猜你喜欢
          • 2021-01-27
          • 1970-01-01
          • 2021-04-27
          • 2022-11-23
          • 1970-01-01
          • 2020-09-19
          • 1970-01-01
          • 1970-01-01
          • 2021-08-29
          相关资源
          最近更新 更多