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

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


person Thorben Croisé    schedule 21.01.2015    source источник


Ответы (3)


Вскоре приказ сохраняется.

Следуя спецификации, на которую вы ссылаетесь, Promise.all(iterable) принимает iterable в качестве параметра и внутренне вызывает PerformPromiseAll(iterator, constructor, resultCapability) с ним, где последний зацикливается на iterable, используя IteratorStep(iterator)< /а>.

Разрешение реализовано через 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));
});

person Etheryte    schedule 21.01.2015
comment
Как бы итерируемый объект не был строго упорядочен? Любая итерация строго упорядочена по порядку, в котором она производит свои значения. - person Benjamin Gruenbaum; 21.01.2015
comment
Примечание. Firefox — единственный браузер, который корректно реализует итерации в промисах. В настоящее время Chrome будет throw исключением, если вы передадите итерируемый объект Promise.all. Кроме того, я не знаю какой-либо реализации обещания пользовательского пространства, которая в настоящее время поддерживает передачу итерируемых объектов, хотя многие обсуждали это и в то время отказывались от этого. - person Benjamin Gruenbaum; 21.01.2015
comment
@BenjaminGruenbaum Object не имеет поведения итерации по умолчанию (поскольку оно не строго упорядочено), а это означает, что если вы используете его вместе с итерируемым интерфейсом, порядок может быть неоднозначным в зависимости от реализации. - person Etheryte; 21.01.2015
comment
Итерация всегда строго упорядочена - порядок просто не определен. Я тут придираюсь и можно сказать, что порядок произвольный, но тем не менее это порядок. - person Benjamin Gruenbaum; 21.01.2015
comment
@BenjaminGruenbaum Разве нельзя иметь итерируемый объект, который производит два разных заказа при двукратном повторении? Например, колода карт, которая при повторении выдает карты в случайном порядке? Я не знаю, подходит ли здесь строго упорядоченная терминология, но не все итерации имеют фиксированный порядок. Поэтому я считаю разумным сказать, что итераторы строго упорядочены (при условии, что это правильный термин), а итераторы — нет. - person JLRishe; 21.01.2015
comment
@JLRishe Думаю, вы правы, действительно упорядочены итераторы, а не итераторы. - person Benjamin Gruenbaum; 21.01.2015
comment
Стоит отметить, что обещания не связаны друг с другом. Хотя вы получите разрешение в том же порядке, нет никакой гарантии, когда обещания будут выполнены. Другими словами, Promise.all нельзя использовать для запуска массива обещаний по порядку, одно за другим. Обещания, загруженные в итератор, должны быть независимы друг от друга, чтобы это работало предсказуемо. - person Andrew Eddie; 09.04.2015
comment
@AndrewEddie Хотя мне кажется естественным, что это основной смысл использования Promise.all, ваш комментарий - это хорошая вещь, на которую стоит обратить внимание. - person Etheryte; 23.10.2015
comment
Кажется, это также верно для $ q в angular, если кому-то нужно знать. - person Liam Middleton; 08.02.2018

Как уже говорилось в предыдущих ответах, 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 утверждения Promises отображаются в таком порядке:

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

Если промисы являются вызовами AJAX, то удаленный сервер получит эти значения в указанном порядке. Но на стороне клиента Promise.all обеспечивает упорядочение результатов в соответствии с исходными позициями массива myPromises. Вот почему окончательный результат:

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

Если вы хотите также гарантировать фактическое выполнение ваших промисов, вам понадобится такая концепция, как очередь промисов. Вот пример использования p-queue (будьте осторожны, вам нужно обернуть все промисы в функции):

Последовательная очередь обещаний

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)']
person Benny Neugebauer    schedule 15.02.2018
comment
отличный ответ, особенно с использованием PQueue - person ironstein; 08.03.2018
comment
Мне нужна очередь последовательных обещаний, но как это сделать, если я должен сделать это из результатов sql-записей? в для? в то время как ?, нет альтернативы в ES2017 нашему ES2018? - person stackdave; 07.12.2018
comment
PQueue помог мне! Благодарю вас! :) - person podeig; 02.07.2020

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

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

person Bergi    schedule 21.01.2015
comment
Странно, сегодня я видел видео на YouTube, в котором говорилось, что порядок вывода определяется первым, кто разрешил, затем вторым, затем ..... Я думаю, видео ОП было неправильным? - person Royi Namir; 15.11.2015
comment
@RoyiNamir: Очевидно, он был. - person Bergi; 15.11.2015
comment
@Озил Ват? Хронологический порядок решения абсолютно не имеет значения, когда все обещания выполняются. Порядок значений в результирующем массиве такой же, как и во входном массиве промисов. Если это не так, вам следует переключиться на правильную реализацию промиса. - person Bergi; 21.09.2017