Promise.all: Порядок разрешенных значений

190

Глядя на MDN, похоже, что valuesпереданный в then()обратный вызов Promise.all содержит значения в порядке обещаний. Например:

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: Запуск подобного кода показал, что это похоже на правду, хотя это, конечно, не доказательство - это могло быть совпадением.

Торбен Круазе
источник

Ответы:

274

В скором времени порядок сохраняется .

Следуя спецификации, с которой вы связались, 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
Как итерация не будет строго упорядочена? Любая итерация «строго упорядочена» по порядку, в котором она производит свои значения.
Бенджамин Грюнбаум
Примечание. Firefox - единственный браузер, правильно реализующий итерации в обещаниях. В настоящее время Chrome будет throwисключением, если вы передадите итерируемое значение Promise.all. Кроме того, я не знаю какой-либо реализации обещания пользовательского пространства, которая в настоящее время поддерживает передачу итераций, хотя многие обсуждали это и в то время решали против этого.
Бенджамин Грюнбаум
3
@BenjaminGruenbaum Разве нельзя иметь итерацию, которая производит два разных порядка после повторения дважды? Например, колода карт, которая производит карты в случайном порядке, когда она повторяется? Я не знаю, является ли здесь «строго упорядоченный» правильной терминологией, но не все итерации имеют фиксированный порядок. Поэтому я думаю, что разумно сказать, что итераторы «строго упорядочены» (при условии, что это правильный термин), а итераторы - нет.
JLRishe
3
@JLRishe Полагаю, вы правы, это действительно итераторы, которые упорядочены - итерации нет.
Бенджамин Грюнбаум
8
Стоит отметить, что обещания не цепляются. Хотя вы получите разрешение в том же порядке, нет никаких гарантий относительно того, когда будут выполнены обещания. Другими словами, Promise.allнельзя использовать для запуска массива обещаний по порядку, одно за другим. Обещания, загруженные в итератор, должны быть независимы друг от друга, чтобы это работало предсказуемо.
Эндрю Эдди
49

Как уже говорилось в предыдущих ответах, Promise.allагрегирует все разрешенные значения в массив, соответствующий порядку ввода исходных обещаний (см. Агрегирование обещаний ).

Однако я хотел бы отметить, что заказ сохраняется только на стороне клиента!

Разработчику кажется, что Обещания были выполнены по порядку, но в действительности Обещания обрабатываются с разной скоростью. Это важно знать, когда вы работаете с удаленным бэкэндом, потому что бэкэнд может получать ваши обещания в другом порядке.

Вот пример, который демонстрирует проблему с использованием тайм-аутов:

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)

В коде, показанном выше, три Обещания (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-очереди (будьте осторожны, вам нужно обернуть все обещания в функции):

Очередь обещаний

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?
стекад
PQueue помог мне! Спасибо! :)
podeig
28

Да, значения в resultsтом же порядке, что и promises.

Можно сослаться на спецификацию ES6Promise.all , хотя она немного запутанная из-за используемого итератора api и универсального конструктора обещаний. Однако вы заметите, что у каждого обратного вызова распознавателя есть [[index]]атрибут, который создается в итерации массива обещаний и используется для установки значений в массиве результатов.

Берги
источник
Странно, я сегодня посмотрел видео на YouTube, в котором говорилось, что порядок вывода определяется первым, кто разрешил, потом вторым, потом ..... Полагаю, видео ОП было неверным?
Рой Намир
1
@RoyiNamir: Видимо, он был.
Берги
@ Озил Ват? Хронологический порядок разрешения абсолютно не имеет значения, когда все обещания выполняются. Порядок значений в массиве результатов такой же, как во входном массиве обещаний. Если это не так, вам следует переключиться на правильную реализацию обещаний.
Берги