【问题标题】:Promise.all: Order of resolved valuesPromise.all:解析值的顺序
【发布时间】:2015-03-19 22:13:16
【问题描述】:

查看MDN,看起来像传递给Promise.all 的then() 回调的values 包含按承诺顺序排列的值。例如:

var somePromises = [1, 2, 3, 4, 5].map(Promise.resolve);
return Promise.all(somePromises).then(function(results) {
  console.log(results) //  is [1, 2, 3, 4, 5] the guaranteed result?
});

任何人都可以引用规范说明values 的顺序吗?

PS:运行这样的代码表明这似乎是真的,尽管这当然不能证明——这可能是巧合。

【问题讨论】:

    标签: javascript promise es6-promise


    【解决方案1】:

    很快,the order is preserved

    按照您链接的规范,Promise.all(iterable)iterable 作为参数并在内部调用PerformPromiseAll(iterator, constructor, resultCapability),后者使用IteratorStep(iterator) 循环iterable

    解析是通过Promise.all() Resolve 实现的,其中每个已解析的promise 都有一个内部[[Index]] 槽,它在原始输入中标记了promise 的索引。


    这意味着输出是严格排序的,因为你传递给 Promise.all() 的迭代是严格排序的(例如,一个数组)。

    您可以在下面的小提琴 (ES6) 中看到这一点:

    // Used to display results
    const write = msg => {
      document.body.appendChild(document.createElement('div')).innerHTML = msg;
    };
    
    // Different speed async operations
    const slow = new Promise(resolve => {
      setTimeout(resolve, 200, 'slow');
    });
    const instant = 'instant';
    const quick = new Promise(resolve => {
      setTimeout(resolve, 50, 'quick');
    });
    
    // The order is preserved regardless of what resolved first
    Promise.all([slow, instant, quick]).then(responses => {
      responses.map(response => write(response));
    });

    【讨论】:

    • 如何不严格排序可迭代对象?任何可迭代对象都按照其产生值的顺序“严格排序”。
    • 注意 - Firefox 是唯一在 Promise 中正确实现迭代的浏览器。如果您将一个可迭代对象传递给Promise.all,Chrome 目前将向throw 发送一个异常。此外,我不知道目前有任何支持传递可迭代对象的用户态承诺实现,尽管当时许多人对此进行了辩论并决定反对它。
    • @BenjaminGruenbaum 是否有可能在迭代两次后产生两个不同的订单?例如,一副牌在迭代时以随机顺序产生牌?我不知道“严格排序”是否是正确的术语,但并非所有迭代都有固定的顺序。所以我认为说 iterators 是“严格排序的”(假设这是正确的术语)是合理的,但 iterables 不是。
    • @JLRishe 我想你是对的,确实是迭代器是有序的——迭代器不是。
    • 值得注意的是,promise 没有链接。虽然您将以相同的顺序获得解决方案,但无法保证何时执行承诺。换句话说,Promise.all 不能用于依次运行一组 Promise,一个接一个。加载到迭代器中的 Promise 需要彼此独立才能使其工作可预测。
    【解决方案2】:

    正如前面的答案已经说明的那样,Promise.all 将所有已解析的值聚合到一个与原始 Promises 的输入顺序相对应的数组中(请参阅Aggregating Promises)。

    但是,我想指出的是,订单只保留在客户端!

    对于开发人员来说,Promise 看起来是按顺序实现的,但实际上,Promise 的处理速度不同。了解何时使用远程后端很重要,因为后端可能会以不同的顺序接收您的 Promise。

    这是一个使用超时来演示问题的示例:

    Promise.all

    const myPromises = [
      new Promise((resolve) => setTimeout(() => {resolve('A (slow)'); console.log('A (slow)')}, 1000)),
      new Promise((resolve) => setTimeout(() => {resolve('B (slower)'); console.log('B (slower)')}, 2000)),
      new Promise((resolve) => setTimeout(() => {resolve('C (fast)'); console.log('C (fast)')}, 10))
    ];
    
    Promise.all(myPromises).then(console.log)

    在上面显示的代码中,三个 Promise(A、B、C)被赋予了Promise.all。三个 Promise 以不同的速度执行(C 是最快的,B 是最慢的)。这就是为什么 Promise 的 console.log 语句按此顺序显示的原因:

    C (fast) 
    A (slow)
    B (slower)
    

    如果 Promises 是 AJAX 调用,那么远程后端将按此顺序接收这些值。但在客户端Promise.all 确保结果按照myPromises 数组的原始位置排序。这就是为什么最终的结果是:

    ['A (slow)', 'B (slower)', 'C (fast)']
    

    如果您还想保证 Promise 的实际执行,那么您需要一个类似于 Promise 队列的概念。这是一个使用p-queue 的示例(注意,您需要将所有 Promises 包装在函数中):

    顺序承诺队列

    const PQueue = require('p-queue');
    const queue = new PQueue({concurrency: 1});
    
    // Thunked Promises:
    const myPromises = [
      () => new Promise((resolve) => setTimeout(() => {
        resolve('A (slow)');
        console.log('A (slow)');
      }, 1000)),
      () => new Promise((resolve) => setTimeout(() => {
        resolve('B (slower)');
        console.log('B (slower)');
      }, 2000)),
      () => new Promise((resolve) => setTimeout(() => {
        resolve('C (fast)');
        console.log('C (fast)');
      }, 10))
    ];
    
    queue.addAll(myPromises).then(console.log);
    

    结果

    A (slow)
    B (slower)
    C (fast)
    
    ['A (slow)', 'B (slower)', 'C (fast)']
    

    【讨论】:

    • 很好的答案,特别是使用 PQueue
    • 我需要一个顺序承诺队列,但是如果我必须从结果 sql 记录中执行它怎么办?在一个为?虽然?,在 ES2017 我们的 ES2018 中别无选择?
    • PQueue 帮助了我!谢谢! :)
    • this 和 for-of-loop with await each promise in order 有什么区别?
    【解决方案3】:

    是的,results 中的值与promises 的顺序相同。

    有人可能会引用ES6 spec on Promise.all,尽管由于使用了迭代器 api 和通用 Promise 构造函数,它有点令人费解。但是,您会注意到每个解析器回调都有一个 [[index]] 属性,该属性是在 promise-array 迭代中创建的,用于设置结果数组的值。

    【讨论】:

    • 奇怪,我今天看到一个youtube视频说输出顺序是由第一个解决的,然后第二个,然后......我猜视频OP错了?
    • @RoyiNamir:显然他是。
    • @Ozil Wat?当所有承诺都实现时,解决的时间顺序绝对无关紧要。结果数组中值的顺序与输入的承诺数组中的顺序相同。如果不是,您应该切换到正确的 Promise 实现。
    猜你喜欢
    • 2019-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多