Хабрахабр

[Перевод] Promise.allSettled

На 71-м митинге Ecma TC39 будет рассматриваться проект и эталонная реализация Promise.allSettled — третьего из четырех основных комбинаторов промисов.

Авторы: Джейсон Вильямс (BBC), Роберт Памли (Bloomberg), Матиас Байненс (Google)
Чемпион: Матиас Байненс (Google)
Этап: 3

Для любителей подкастов, продублировано на YouTube.

В мире промисов существует четыре основных комбинатора:

  • Promise.all. ES2015. Замыкается на первом отклоненном/rejected промисе.
  • Promise.race. ES2015. Замыкается на первом хоть как-то разрешенном/settled промисе.
  • Promise.any. Stage 1. Замыкается на первом удовлетворенном/fulfilled промисе.
  • Promise.allSettled. Stage 3 → Stage 4. Не замыкается.

Все они широко представлены в обычных пользовательских библиотеках, каждый из них полезен сам по себе и подходит в различных ситуациях.

Остальные комбинаторы промисов замыкаются (short-circuit), выбрасывая результаты входящих значений, проигравших в гонке за определённым состоянием системы. Основное применение этого комбинатора наступает, когда хочется выполнить действие сразу после завершения множества запросов, вне зависимости, закончились ли они успехом или неудачей. Promise.allSettled уникален тем, что всегда ожидает всех, за кого отвечает.

Promise.allSettled возвращает промис, который выполняется с возвращением массива снапшотов состояний промисов, но лишь только после того, как совершенно все исходные промисы разрешены (settled).

когда он либо удовлетворён, либо отклонён — одно из двух. Мы говорим, что промис разрешен (settled), если он не подвис в ожидании (pending), т.е. Чтобы разобраться в терминологии, взгляните на старый документ States and Fates.

Список будет ниже. А ещё, это имя, allSettled, широко используется в существующих библиотеках, реализующих данную функциональность.

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

function reflect(promise) ; }, (error) => { return { status: 'rejected', reason: error }; } );
} const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.all(promises.map(reflect));
const successfulPromises = results.filter(p => p.status === 'fulfilled');

Новое API выглядит так: Предлагаемое API позволяет разработчику обработать эти варианты, без необходимости создавать функцию reflect самостоятельно, или заниматься хранением результатов во временных переменных.

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
const successfulPromises = results.filter(p => p.status === 'fulfilled');

allSettled позволяет сделать это так же просто. Если же нам почему-то нужны отклонённые промисы, то вероятно, нужно собрать причины произошедшего.

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ]; const results = await Promise.allSettled(promises);
const errors = results .filter(p => p.status === 'rejected') .map(p => p.reason);

Это важно, когда хочется в будущем заняться постепенным улучшением. Довольно распространённым является желание знать, что все запросы выполнились, вне зависимости от состояния каждого из них. Не всегда нам нужно получить от API ответ.

const urls = [ /* ... */ ];
const requests = urls.map(x => fetch(x)); // Представьте, что-то из этого увенчается успехом, а что-то - нет. // Вот этот комбинатор остановится на первом же отказе, а ответы потеряются.
try { await Promise.all(requests); console.log('Все запросы вернулись, можно убрать полоску загрузки.');
} catch { console.log('Какой-то из запросов явно отвалился, но другие могут продолжать работать. Ой.');
}

С использованием Promise.allSettled можно написать нечто, что больше соответствует нашим ожиданиям.

// Мы точно знаем, что все запросы к API уже отработали.
Promise.allSettled(requests).finally(() => { console.log('Все запросы завершены: успешно или с ошибкой, сейчас всё равно'); removeLoadingIndicator();
});

Поскольку не существует универсального механизма, совместимого сразу со множеством языков, этот документ следует прмеру наименований из библиотек, перечисленных выше. Похожая функциональность существует и в других языках, под другими именами. Вот как это выглядит в других языках:

  • Rust — futures::join;
  • C# — Task.WhenAll. Можно использовать либо try/catch, либо TaskContinuationOptions.OnlyOnFaulted;
  • Python — asyncio.wait с опцией ALL_COMPLETED
  • Java — CompletableFuture.allOf

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть