Promise.allSettled

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.


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


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


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


Введение и мотивация


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


  • Promise.all (ES2015; замыкается на первом отклоненном промисе)
  • Promise.race (ES2015; замыкается на первом разрешенном промисе)
  • Promise.any (Stage 1; замыкается на первом удовлетворенном промисе)
  • Promise.allSettled (Stage 3 → Stage 4; не замыкается)

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


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


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


Откуда взялось название allSettled?


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


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


Примеры


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


function reflect(promise) {
  return promise.then(
    (v) => {
      return { status: 'fulfilled', value: v };
    },
    (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 позволяет разработчику обработать эти варианты, без необходимости создавать функцию reflect самостоятельно, или заниматься хранением результатов во временных переменных. Новое API выглядит так:


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();
});

Пользовательские реализации


  • https://www.npmjs.com/package/promise.allsettled
  • https://www.npmjs.com/package/q
  • https://www.npmjs.com/package/rsvp
  • http://bluebirdjs.com/docs/api/reflect.html
  • https://www.npmjs.com/package/promise-settle
  • https://github.com/cujojs/when/blob/master/docs/api.md#whensettle
  • https://www.npmjs.com/package/es2015-promise.allsettled
  • https://www.npmjs.com/package/promise-all-settled
  • https://www.npmjs.com/package/maybe

В других языках


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


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

Материалы для дальнейшего изучения


  • https://www.bennadel.com/blog/3289-implementing-q-s-allsettled-promise-method-in-bluebird.htm
  • https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md
  • http://exploringjs.com/es6/ch_promises.html
  • https://github.com/kriskowal/q/issues/257

Минутки со встреч TC39


  • September 2018
  • January 2019
  • March 2019
  • July 2019 — скоро будет

Спецификация


  • Исходник на Ecmarkup
  • В виде HTML

Реализации


  • V8, Chrome 76
  • SpiderMonkey, Firefox 68 Nightly
  • JavaScriptCore, Safari TP 85 and Safari 13 Beta
  • Chakra
  • Основной полифилл
Источник: https://habr.com/ru/post/459970/


Интересные статьи

Интересные статьи

Один из ключевых сценариев работы в CRM это общение с клиентом в удобном для него канале. По почте, по телефону, по SMS или в мессенджере. Особенно выделяется WhatsApp — интеграцию с ...
Ваш сайт работает на 1С-Битрикс? Каждому клиенту вы даёте собственную скидку или назначаете персональную цену на товар? Со временем в вашей 1С сложилась непростая логика ценообразования и формирования...
Если в вашей компании хотя бы два сотрудника, отвечающих за работу со сделками в Битрикс24, рано или поздно возникает вопрос распределения лидов между ними.
Реализация ORM в ядре D7 — очередная интересная, перспективная, но как обычно плохо документированная разработка от 1с-Битрикс :) Призвана она абстрагировать разработчика от механики работы с табл...
Один из самых острых вопросов при разработке на Битрикс - это миграции базы данных. Какие же способы облегчить эту задачу есть на данный момент?