Promise.all:解析值的顺序

Promise.all: Order of resolved values

本文关键字:顺序 all Promise      更新时间:2023-09-26

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

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:像这样运行代码表明这似乎是真的,尽管这当然不是证据 - 这可能是巧合。

很快,顺序被保留

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

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


所有这些都意味着输出是严格排序的,因为您传递给 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));
});

正如前面的答案已经指出的,Promise.all 将所有解析的值聚合为对应于原始 Promise 输入顺序的数组(请参阅聚合 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))
];
Promise.all(myPromises).then(console.log)

在上面显示的代码中,三个承诺(A,B,C)被赋予Promise.all。三个承诺以不同的速度执行(C是最快的,B是最慢的)。这就是为什么承诺的console.log陈述按以下顺序显示:

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

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

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

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

顺序承诺队列

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)']
是的

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

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