Promise.all:解析值的顺序

Posted

技术标签:

【中文标题】Promise.all:解析值的顺序【英文标题】:Promise.all: Order of resolved values 【发布时间】: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:运行这样的代码表明这似乎是真的,尽管这当然不能证明——这可能是巧合。

【问题讨论】:

【参考方案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 实现。

以上是关于Promise.all:解析值的顺序的主要内容,如果未能解决你的问题,请参考以下文章

es6 promise.all()

与 Promise.all() 中的解析相比,为啥在 while 循环中单独解析 Promise 数组时解析更慢? [复制]

Promise.all 的顺序执行

JavaScript Promise.all - 如何检查解析状态?

JavaScript 中的 Promise.all:如何获得所有承诺的解析值?

promise.all 解说