Javascript метод promise.all()

Promise.all

Допустим, нам нужно запустить множество промисов параллельно и дождаться, пока все они выполнятся.


Например, параллельно загрузить несколько файлов и обработать результат, когда он готов.

Для этого как раз и пригодится .

Синтаксис:

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

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

Например, , представленный ниже, выполнится спустя 3 секунды, его результатом будет массив :

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

Часто применяемый трюк – пропустить массив данных через map-функцию, которая для каждого элемента создаст задачу-промис, и затем обернёт получившийся массив в .

Например, если у нас есть массив ссылок, то мы можем загрузить их вот так:

А вот пример побольше, с получением информации о пользователях GitHub по их логинам из массива (мы могли бы получать массив товаров по их идентификаторам, логика та же):

Если любой из промисов завершится с ошибкой, то промис, возвращённый , немедленно завершается с этой ошибкой.

Например:

Здесь второй промис завершится с ошибкой через 2 секунды. Это приведёт к немедленной ошибке в , так что выполнится : ошибка этого промиса становится ошибкой всего .

В случае ошибки, остальные результаты игнорируются

Если один промис завершается с ошибкой, то весь завершается с ней, полностью забывая про остальные промисы в списке. Их результаты игнорируются.

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

ничего не делает для их отмены, так как в промисах вообще нет концепии «отмены». В главе Fetch: прерывание запроса мы рассмотрим , который помогает с этим, но он не является частью Promise API.

разрешает передавать не-промисы в (перебираемом объекте)

Обычно, принимает перебираемый объект промисов (чаще всего массив). Но если любой из этих объектов не является промисом, он передаётся в итоговый массив «как есть».

Например, здесь результат:

Таким образом, мы можем передавать уже готовые значения, которые не являются промисами, в , иногда это бывает удобно.

Example: loadScript

Let’s use this feature with the promisified , defined in the , to load scripts one by one, in sequence:

This code can be made bit shorter with arrow functions:

Here each call returns a promise, and the next runs when it resolves. Then it initiates the loading of the next script. So scripts are loaded one after another.

We can add more asynchronous actions to the chain. Please note that the code is still “flat” — it grows down, not to the right. There are no signs of the “pyramid of doom”.

Technically, we could add directly to each , like this:

This code does the same: loads 3 scripts in sequence. But it “grows to the right”. So we have the same problem as with callbacks.

People who start to use promises sometimes don’t know about chaining, so they write it this way. Generally, chaining is preferred.

Sometimes it’s ok to write directly, because the nested function has access to the outer scope. In the example above the most nested callback has access to all variables , , . But that’s an exception rather than a rule.

Thenables

To be precise, a handler may return not exactly a promise, but a so-called “thenable” object – an arbitrary object that has a method . It will be treated the same way as a promise.

The idea is that 3rd-party libraries may implement “promise-compatible” objects of their own. They can have an extended set of methods, but also be compatible with native promises, because they implement .

Here’s an example of a thenable object:

JavaScript checks the object returned by the handler in line : if it has a callable method named , then it calls that method providing native functions , as arguments (similar to an executor) and waits until one of them is called. In the example above is called after 1 second . Then the result is passed further down the chain.

This feature allows us to integrate custom objects with promise chains without having to inherit from .

Создаем Promise-объект с помощью $.deferred


Каждый Promise-объект в jQuery начинается с Deferred-объекта. Deferred-объект — это Promise-объект с методами, которые позволяют владельцу изменять его состояние. Все остальные Promises-объекты являются копиями «только для чтения» Deferred-объекта.  Чтобы создать Deferred-объект нужно использовать конструктор :

var deferred = new $.Deferred();
 
deferred.state();  // "pending"
deferred.resolve();
deferred.state();  // "resolved"
deferred.reject(); // нет эффекта, потому что Promise-объект уже изменил состояние

Примечание: метод был добавлен в jQuery 1.7. В версиях 1.5/1.6 надо использовать методы и .

Мы можем получить «чистый» Promise-объект вызвав метод . Результат будет идентичен Deferred-объекту за исключением отсутствия методов  и .

var deferred = new $.Deferred();
var promise = deferred.promise();
 
promise.state();  // "pending"
deferred.reject();
promise.state();  // "rejected"

Метод существует для реализации инкапсуляции: если возвращать Deferred-объект из функции, его состояние может быть изменено вызывающим кодом. Но если вы возвращаете «чистый» Promise-объект, соответствующий Deferred-объекту, то вызывающий код сможет только прочитать его состояние и привязать возвратную функцию. В jQuery используется такой подход: «чистый» Promise-объект возвращается из методов AJAX:

var gettingProducts = $.get("/products");
 
gettingProducts.state();  // "pending"
gettingProducts.resolve;  // неопределенно

Complex async code made easier #

Right, let’s code some things. Say we want to:

  1. Start a spinner to indicate loading
  2. Fetch some JSON for a story, which gives us the title, and urls for each chapter
  3. Add title to the page
  4. Fetch each chapter
  5. Add the story to the page
  6. Stop the spinner

… but also tell the user if something went wrong along the way. We’ll want to stop the spinner at that point too, else it’ll keep on spinning, get dizzy, and crash into some other UI.

Of course, you wouldn’t use JavaScript to deliver a story, serving as HTML is faster, but this pattern is pretty common when dealing with APIs: Multiple data fetches, then do something when it’s all done.

To start with, let’s deal with fetching data from the network:

Awaiting a promise

In order to use a promise, we must somehow be able to wait for it to be fulfilled or rejected. The way to do this is using (see warning at the end of this section if attempting to run these samples).

With this in mind, it’s easy to re-write our earlier function to use promises:

This still has lots of error handling code (we’ll see how we can improve on that in the next section) but it’s a lot less error prone to write, and we no longer have a strange extra parameter.

Non Standard

Note that (used in the examples in this section) has not been standardised. It is supported by most major promise libraries though, and is useful both as a teaching aid and in production code. I recommend using it along with the following polyfill (minified / unminified):

Цепочки промисов

Да, в промисах есть цепочки.

Давайте представим, что вы ребенок и обещали своему другу, что покажете ему новый телефон, когда вам его купят. Это будет ещё один промис.

// 2й промисvar showOff = function (phone) {    return new Promise(        function (resolve, reject) {            var message = 'Hey friend, I have a new ' +                phone.color + ' ' + phone.brand + ' phone';            resolve(message);        }    );};

В этом примере вы уже наверное поняли, что мы не вызывали . Так как, в принципе, это опционально.

Мы вообще можем сократить этот пример, используя .

// 2й промисvar showOff = function (phone) {var message = 'Hey friend, I have a new ' +  phone.color + ' ' + phone.brand + ' phone';return Promise.resolve(message);};

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

// Вызываем промисvar askMom = function () {    willIGetNewPhone    .then(showOff) // связываем    .then(function (fulfilled) {            console.log(fulfilled);         // output: 'Hey friend, I have a new black Samsung phone.'        })        .catch(function (error) {            // oops, mom don't buy it            console.log(error.message);         // output: 'mom is not happy'        });};

Вот так легко связывать промисы.

Промисы и асинхронность

Промисы асинхронны. Давайте выведем сообщение перед и после вызовом промиса.

// вызываем наш промисvar askMom = function () {    console.log('before asking Mom'); // Выводим в консоль до    willIGetNewPhone        .then(showOff)        .then(function (fulfilled) {            console.log(fulfilled);        })        .catch(function (error) {            console.log(error.message);        });    console.log('after asking mom'); // Выводим в консоль после}

Какова последовательность ожидаемого вывода? Возможно вы ожидали.

1. before asking Mom2. Hey friend, I have a new black Samsung phone.3. after asking mom

Но на самом деле вывод будет таким:

1. before asking Mom2. after asking mom3. Hey friend, I have a new black Samsung phone.

Почему? Потому что жизнь (или JS) никого не ждёт.

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

Промисы в ES5, ES6/2015, ES7/Next

ES 5 — поддерживают почти все браузеры. Демо код работает в ES5 среде (Все основные браузеры + NodeJS), если бы вы подключили библиотеку промисов Bluebird. Почему так? Потому что ES5 не поддерживает промисы из коробки. Другая знаменитая библиотека промисов это Q, от Криса Коваля.

ES6 / ES2015 — демо код сработает прямо из коробки, так как ES6 поддерживает промисы естественным путём. Более того, с ES6 функциями, мы можем ещё круче упростить код с помощью и использовать const и .

/_ ES6 _/const isMomHappy = true;// Промисconst willIGetNewPhone = new Promise(    (resolve, reject) => { // fat arrow        if (isMomHappy) {            const phone = {                brand: 'Samsung',                color: 'black'            };            resolve(phone);        } else {            const reason = new Error('mom is not happy');            reject(reason);        }    });const showOff = function (phone) {    const message = 'Hey friend, I have a new ' +                phone.color + ' ' + phone.brand + ' phone';    return Promise.resolve(message);};// Вызываем промисconst askMom = function () {    willIGetNewPhone        .then(showOff)        .then(fulfilled => console.log(fulfilled)) // fat arrow        .catch(error => console.log(error.message)); // fat arrow};askMom();

Обратите внимание, что все заменены на. Все были упрощены на

ES7 — делают синтаксис визуально лучше. ES7 представил async и await синтаксис. Это делает асинхронный синтаксис визуально лучше и проще для понимания, без и . Перепишем свой пример с ES7 синтаксисом.

/_ ES7 _/const isMomHappy = true;// Промисconst willIGetNewPhone = new Promise(    (resolve, reject) => {        if (isMomHappy) {            const phone = {                brand: 'Samsung',                color: 'black'            };            resolve(phone);        } else {            const reason = new Error('mom is not happy');            reject(reason);        }    });// 2й промисasync function showOff(phone) {    return new Promise(        (resolve, reject) => {            var message = 'Hey friend, I have a new ' +                phone.color + ' ' + phone.brand + ' phone';            resolve(message);        }    );};// Вызываем промисasync function askMom() {    try {        console.log('before asking Mom');        let phone = await willIGetNewPhone;        let message = await showOff(phone);        console.log(message);        console.log('after asking mom');    }    catch (error) {        console.log(error.message);    }}(async () => {    await askMom();})();

1. Всегда, когда вам нужно возвратить промис в функцию, вы ставите спереди к этой функции. Для примера,

2. Всякий раз, когда вам надо вызвать промис, вам надо вставить . Для примера, и

3. Используйте , чтобы словить ошибку промиса, отклоненную промисом.

Requirements

Promise States

A promise must be in one of three states: pending, fulfilled, or rejected.

  1. When pending, a promise:
    1. may transition to either the fulfilled or rejected state.
  2. When fulfilled, a promise:
    1. must not transition to any other state.
    2. must have a value, which must not change.
  3. When rejected, a promise:
    1. must not transition to any other state.
    2. must have a reason, which must not change.

Here, “must not change” means immutable identity (i.e. ), but does not imply deep immutability.

The Method

A promise must provide a method to access its current or eventual value or reason.

A promise’s method accepts two arguments:

  1. Both and are optional arguments:
    1. If is not a function, it must be ignored.
    2. If is not a function, it must be ignored.
  2. If is a function:
    1. it must be called after is fulfilled, with ’s value as its first argument.
    2. it must not be called before is fulfilled.
    3. it must not be called more than once.
  3. If is a function,
    1. it must be called after is rejected, with ’s reason as its first argument.
    2. it must not be called before is rejected.
    3. it must not be called more than once.
  4. or must not be called until the stack contains only platform code. [].
  5. and must be called as functions (i.e. with no value). []
  6. may be called multiple times on the same promise.
    1. If/when is fulfilled, all respective callbacks must execute in the order of their originating calls to .
    2. If/when is rejected, all respective callbacks must execute in the order of their originating calls to .
  7. must return a promise [].

    1. If either or returns a value , run the Promise Resolution Procedure .
    2. If either or throws an exception , must be rejected with as the reason.
    3. If is not a function and is fulfilled, must be fulfilled with the same value as .
    4. If is not a function and is rejected, must be rejected with the same reason as .

The Promise Resolution Procedure

The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as . If is a thenable, it attempts to make adopt the state of , under the assumption that behaves at least somewhat like a promise. Otherwise, it fulfills with the value .

This treatment of thenables allows promise implementations to interoperate, as long as they expose a Promises/A+-compliant method. It also allows Promises/A+ implementations to “assimilate” nonconformant implementations with reasonable methods.

To run , perform the following steps:

  1. If and refer to the same object, reject with a as the reason.
  2. If is a promise, adopt its state []:
    1. If is pending, must remain pending until is fulfilled or rejected.
    2. If/when is fulfilled, fulfill with the same value.
    3. If/when is rejected, reject with the same reason.
  3. Otherwise, if is an object or function,
    1. Let be . []
    2. If retrieving the property results in a thrown exception , reject with as the reason.
    3. If is a function, call it with as , first argument , and second argument , where:
      1. If/when is called with a value , run .
      2. If/when is called with a reason , reject with .
      3. If both and are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
      4. If calling throws an exception ,
        1. If or have been called, ignore it.
        2. Otherwise, reject with as the reason.
    4. If is not a function, fulfill with .
  4. If is not an object or function, fulfill with .

If a promise is resolved with a thenable that participates in a circular thenable chain, such that the recursive nature of eventually causes to be called again, following the above algorithm will lead to infinite recursion. Implementations are encouraged, but not required, to detect such recursion and reject with an informative as the reason. []

Более сложный пример: fetch


Во фронтенд-разработке промисы часто используются, чтобы делать запросы по сети. Давайте рассмотрим один такой пример.

Мы будем использовать метод fetch, чтобы подгрузить информацию о пользователях с удалённого сервера. Этот метод имеет много опциональных параметров, разобранных в соответствующих разделах, но базовый синтаксис весьма прост:

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

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

Код ниже запрашивает файл и загружает его содержимое с сервера:

Есть также метод , который читает данные в формате JSON. Он больше подходит для нашего примера, так что давайте использовать его.

Мы также применим стрелочные функции для более компактной записи кода:

Теперь давайте что-нибудь сделаем с полученными данными о пользователе.

Например, мы можем послать запрос на GitHub, чтобы загрузить данные из профиля пользователя и показать его аватар:

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

Чтобы сделать наш код расширяемым, нам нужно возвращать ещё один промис, который выполняется после того, как завершается показ аватара.

Примерно так:

То есть, обработчик в строке будет возвращать , который перейдёт в состояние «выполнен» только после того, как в будет вызвана .

Соответственно, следующий по цепочке будет ждать этого.

Как правило, все асинхронные действия должны возвращать промис.

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

И, наконец, давайте разобьём написанный код на отдельные функции, пригодные для повторного использования:

Motivation

Consider the following synchronous JavaScript function to read a file and parse it as JSON. It is simple and easy to read, but you wouldn’t want to use it in most applications as it is blocking. This means that while you are reading the file from disk (a slow operation) nothing else can happen.

To make our application performant and responsive, we need to make all the operations that involve IO be asynchronous. The simplest way to do this would be to use a callback. However, a naive implementation will probably go wrong:

  • The extra parameter confuses our idea of what is input and what is the return value.
  • It doesn’t work at all with control flow primitives.
  • It doesn’t handle errors thrown by

We need to handle errors thrown by but we also need to be careful not to handle errors thrown by the function. By the time we’ve done all of this our code is a mess of error handling:

Despite all this mess of error handling code, we are still left with the problem of the extra parameter hanging around. Promises help you naturally handle errors, and write cleaner code by not having parameters, and without modifying the underlying architecture (i.e. you can implement them in pure JavaScript and use them to wrap existing asynchronous operations).

Перевод функций в стиле Node.js на использование объектов Promise

В JavaScript не все асинхронные функции и библиотеки поддерживают объекты Promise изначально. Обычно типичные функции, основанные на обратных вызовах, требуется преобразовать так, чтобы они возвращали объекты Promise. Этот процесс называется переводом на использование объектов Promise.

К счастью, соглашения об обратных вызовах, используемые на платформе Node.js, позволяют создавать функции, способные переводить любые функции в стиле Node.js на использование объектов Promise. Это несложно осуществить с помощью конструктора объекта Promise. Создадим новую функцию promisify() и добавим ее в модуль utilities.js (чтобы ее можно было использовать в приложении веб­паука):

module.exports.promisify = function(callbackBasedApi) {
  return function promisified() {
    const args = [].slice.call(arguments);
    return new Promise((resolve, reject) => {  //
      args.push((err, result) => {             //
        if(err) {
          return reject(err);                  //
        }
        if(arguments.length <= 2) {            //
          resolve(result);
        } else {
          resolve([].slice.call(arguments, 1));
        }
     });
     callbackBasedApi.apply(null, args);      //
   });
 }
};

Приведенная выше функция возвращает другую функцию – promisifed(), которая является версией callbackBasedApi, возвращающей объект Promise. Вот как она работает:

  1. функция promisifed() создает новый объект с помощью конструктора Promise и немедленно возвращает его;
  2. в функции, что передается конструктору Promise, мы передаем специальную функцию обратного вызова для вызова из callbackBasedApi. Поскольку функция обратного вызова всегда передается в последнем аргументе, мы просто добавляем ее в список аргументов (args) функции promisifed();
  3. если специальная функция обратного вызова получит ошибку, объект Promise немедленно отклоняется;
  4. в случае отсутствия ошибки осуществляется разрешение объекта Promise со значением или массивом значений, в зависимости от количества результатов, переданных функции обратного вызова;
  5. в заключение вызывается callbackBasedApi с созданным списком аргументов.

Реализации Promises/A+

Как в JavaScript, так и в Node.js есть несколько библиотек, реализующих спецификацию Promises/A+. Ниже перечислены наиболее популярные из них:

  • Bluebird (https://npmjs.org/package/bluebird);
  • Q (https://npmjs.org/package/q);
  • RSVP (https://npmjs.org/package/rsvp);
  • Vow (https://npmjs.org/package/vow);
  • When.js (https://npmjs.org/package/when);
  • объекты Promise из ES2015.

По существу, они отличаются только наборами дополнительных возможностей, не предусмотренных стандартом Promises/A+. Как упоминалось выше, этот стандарт определяет модель поведения метода then() и процедуру разрешения объекта Promise, но не регламентирует других функций, например порядка создания объекта Promise на основе асинхронной функции с обратным вызовом.

В примерах ниже мы будем использовать методы, поддерживаемые объектами Promise стандарта ES2015, поскольку они доступны в Node.js, начиная с версии 4, и не требуют подключения внешних библиотек.

Для справки ниже перечислены методы объектов Promise, определяемые стандартом ES2015.

Конструктор (new Promise(function(resolve, reject) {})): создает новый объект Promise, который разрешается или отклоняется в зависимости от функции, переданной в аргументе. Конструктору можно передать следующие аргументы:

  • resolve(obj): позволяет разрешить объект Promise и вернуть результат obj, если obj является значением. Если obj является другим объектом Promise или thenable­объектом, результатом станет результат выполнения obj;
  • reject(err): отклоняет объект Promise с указанной причиной err. В соответствии с соглашением err должен быть экземпляр Error.

Статические методы объекта Promise:

  • Promise.resolve(obj): возвращает новый объект Promise, созданный из thenableобъекта, если obj – thenable­объект, или значение, если obj – значение;
  • Promise.all(iterable): создает объект Promise, который разрешается результатами выполнения, если все элементы итерируемого объекта iterable выполнились, и отклоняется при первом же отклонении любого из элементов. Любой элемент итерируемого объекта может быть объектом Promise, универсальным thenable­объектом или значением;
  • Promise.race(iterable): возвращает объект Promise, разрешаемый или отклоняемый, как только разрешится или будет отклонен хотя бы один из объектов Promise в итерируемом объекте iterable, со значением или причиной этого объекта Promise.

Методы экземпляра Promise:

  • promise.then(onFulflled, onRejected): основной метод объекта Promise. Его модель поведения совместима со стандартом Promises/A+, упомянутым выше;
  • promise.catch(onRejected): удобная синтаксическая конструкция, заменяющая promise.then(undefned, onRejected).

Потребители: then, catch, finally

Объект служит связующим звеном между исполнителем («создающим» кодом или «певцом») и функциями-потребителями («фанатами»), которые получат либо результат, либо ошибку. Функции-потребители могут быть зарегистрированы (подписаны) с помощью методов , и .

Наиболее важный и фундаментальный метод – .

Синтаксис:

Первый аргумент метода – функция, которая выполняется, когда промис переходит в состояние «выполнен успешно», и получает результат.

Второй аргумент – функция, которая выполняется, когда промис переходит в состояние «выполнен с ошибкой», и получает ошибку.

Например, вот реакция на успешно выполненный промис:

Выполнилась первая функция.

А в случае ошибки в промисе – выполнится вторая:


Если мы заинтересованы только в результате успешного выполнения задачи, то в можно передать только одну функцию:

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

Вызов – это сокращённый, «укороченный» вариант .

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

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

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

Например:

Но это не совсем псевдоним , как можно было подумать. Существует несколько важных отличий:

  1. Обработчик, вызываемый из , не имеет аргументов. В мы не знаем, как был завершён промис. И это нормально, потому что обычно наша задача – выполнить «общие» завершающие процедуры.

  2. Обработчик «пропускает» результат или ошибку дальше, к последующим обработчикам.

    Например, здесь результат проходит через к :

    А здесь ошибка из промиса проходит через к :

    Это очень удобно, потому что не предназначен для обработки результата промиса. Так что он просто пропускает его через себя дальше.

    Мы более подробно поговорим о создании цепочек промисов и передаче результатов между обработчиками в следующей главе.

  3. Последнее, но не менее значимое: вызов удобнее, чем – не надо дублировать функции f.

На завершённых промисах обработчики запускаются сразу

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

Теперь рассмотрим несколько практических примеров того, как промисы могут облегчить нам написание асинхронного кода.

Создание промисов

Создание объектов происходит по такому шаблону:

Выполняющая функция (executor) принимает два аргумента resolve и reject, которые являются колбэками. Промисы используются для обработки асинхронных операций, являющихся блокирующим кодом. Обращения к БД, I/O или API — всё это примеры таких операций, с которыми должна разобраться выполняющая функция. Как только она завершится, она либо исполнит промис (вызовет resolve), либо отклонит его (вызовет reject).

Простой пример:

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

Очевидно, что у промиса могут быть разные состояния:

  1. ожидание (pending): начальное состояние, не исполнен и не отклонен
  2. исполнено (fulfilled): операция завершена успешно
  3. отклонено (rejected): операция завершена с ошибкой

Пример:

Использование промисов

С созданием промисов проблем быть не должно, так что давайте перейдём к их использованию:

Вызов .checkIfDone() начнёт исполнение промиса isDone и дождётся его завершения, вызвав колбэк для результата. В случае ошибки будет вызван метод .catch().

Цепочка промисов

Методы промиса .then() и .catch() сами возвращают промис, для которого можно вновь вызвать .then() или .catch(), создав таким образом цепочку из промисов:

В коде выше каждый промис передаёт результат своего исполнения в следующий .then() и так до полного завершения всей цепочки.

Пример: loadScript

У нас есть функция для загрузки скрипта из предыдущей главы.

Давайте вспомним, как выглядел вариант с колбэками:

Теперь перепишем её, используя .

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

Применение:

Сразу заметно несколько преимуществ перед подходом с использованием колбэков:

Промисы Колбэки
Промисы позволяют делать вещи в естественном порядке. Сперва мы запускаем , и затем () мы пишем, что делать с результатом. У нас должна быть функция на момент вызова . Другими словами, нам нужно знать что делать с результатом до того, как вызовется .
Мы можем вызывать у столько раз, сколько захотим. Каждый раз мы добавляем нового «фаната», новую функцию-подписчика в «список подписок». Больше об этом в следующей главе: Цепочка промисов. Колбэк может быть только один.

Таким образом, промисы позволяют улучшить порядок кода и дают нам гибкость. Но это далеко не всё. Мы узнаем ещё много полезного в последующих главах.

Итого

  • перехватывает все виды ошибок в промисах: будь то вызов или ошибка, брошенная в обработчике при помощи .
  • Необходимо размещать там, где мы хотим обработать ошибки и знаем, как это сделать. Обработчик может проанализировать ошибку (могут быть полезны пользовательские классы ошибок) и пробросить её, если ничего не знает о ней (возможно, это программная ошибка).
  • Можно и совсем не использовать , если нет нормального способа восстановиться после ошибки.
  • В любом случае нам следует использовать обработчик события (для браузеров и аналог для других окружений), чтобы отслеживать необработанные ошибки и информировать о них пользователя (и, возможно, наш сервер), благодаря чему наше приложение никогда не будет «просто умирать».

С этим читают