Promise.all: Порядок вирішених значень


189

Дивлячись на 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 - єдиний браузер, який правильно виконує ітерабелі в обіцянках. Наразі throwв Chrome буде дихання, якщо ви перейдете до нього 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?
stackdave

PQueue допомогла мені! Дякую! :)
podeig

28

Так, значення resultsв такому ж порядку, як і значення promises.

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


Дивно, я сьогодні побачив відео на YouTube, яке говорило, що порядок виведення визначається першим, хто вирішив, а потім другим, потім ..... Я здогадуюсь, що відео ОП було неправильним?
Рой Намір

1
@RoyiNamir: Мабуть, він був.
Бергі

@Ozil Wat? Хронологічний порядок постанови абсолютно не має значення, коли всі обіцянки виконуються. Порядок значень у масиві результатів такий же, як у вхідному масиві обіцянок. Якщо це не так, слід перейти до належної реалізації обіцянок.
Бергі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.