Promise.all:已解析值的顺序


188

查看MDN看起来像values传递给then()Promise 的回调。all包含按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:运行这样的代码表明这似乎是对的,尽管这当然没有证据-可能是巧合。

Answers:


271

不久,订单被保留

按照您链接的规范,Promise.all(iterable)将一个iterable(即支持该Iterator接口的对象)作为参数,然后再调用PerformPromiseAll( iterator, constructor, resultCapability)它,后者iterable使用循环IteratorStep(iterator)
这意味着,如果Promise.all()严格要求传递给您的可迭代对象,则传递给它们后仍将对其进行排序。

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


所有这一切意味着,只要严格地对输入进行排序(例如,数组),就可以严格地将输出作为输入进行排序。

您可以在下面的小提琴(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));
});


1
如何严格地限制迭代器?可迭代由它产生的价值秩序“严格有序的。”
本杰明Gruenbaum

注意-Firefox是唯一正确实现Promise的可浏览器的浏览器。throw如果您将Iterable传递给,则Chrome当前不适用Promise.all。此外,尽管当时许多人对此进行了辩论并决定反对,但我不知道目前是否有任何支持通过迭代的userland promise实现。
本杰明·格林鲍姆

3
@BenjaminGruenbaum不可能有一个可迭代的对象,该可迭代对象在被迭代两次时会产生两个不同的命令吗?例如,一副纸牌在迭代时会随机生成纸牌?我不知道“严格排序”是否是正确的术语,但并非所有可迭代对象都有固定的顺序。因此,我认为可以合理地说迭代器是“严格排序的”(假设这是正确的术语),而迭代器则不是。
JLRishe 2015年

3
@JLRishe我猜你是对的,确实是有序的迭代器-可迭代的不是。
本杰明·格林鲍姆

8
值得注意的是,诺言不会连锁。虽然您将以相同的顺序获得解决方案,但是不能保证何时兑现承诺。换句话说,Promise.all不能用于依次运行一个promise数组。加载到迭代器中的promise必须彼此独立,才能可预测地工作。
安德鲁·埃迪

48

正如前面的答案已经说明的那样,Promise.all使用与原始Promises的输入顺序相对应的数组来聚合所有已解析的值(请参阅聚合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.logPromises语句按以下顺序显示的原因:

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

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

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

如果您还想保证Promises的实际执行,那么您需要一个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)']

2
很好的答案,特别是使用PQueue
ironstein,

我需要一个顺序承诺队列,但是如果必须从结果sql记录中去做该怎么办?在一个?而?,在ES2017中没有其他选择是我们的ES2018?
stackdave

PQueue帮助了我!谢谢!:)
podeig

28

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

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


很奇怪,今天我看了一个youtube视频,说输出顺序是由第一个解决的人决定的,然后是第二个,然后.....我想视频OP是错的吗?
罗伊·纳米尔

1
@RoyiNamir:显然他是。
Bergi 2015年

@厄齐尔·沃特(Ozil Wat)?当所有诺言实现时,解决的时间顺序绝对不重要。结果数组中值的顺序与promise输入数组中的顺序相同。如果不是,则应切换到适当的Promise实现。
Bergi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.