Javascript метод promise.finally()

Transformation / Chaining

Following our example through, what we really want to do is transform
the promise via another operation. In our case, this second operation
is synchronous, but it might just as easily have been an asynchronous
operation. Fortunately, promises have a (fully standardised,
) method for transforming promises
and chaining operations.

Put simply, is to as is to . To put that another
way, use whenever you’re going to do something with the result
(even if that’s just waiting for it to finish) and use
whenever you aren’t planning on doing anything with the result.

Now we can re-write our original example as simply:

Since is just a function, we could re-write this as:

This is very close to the simple synchronous example we started out with.

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

Базовое использования метода

const promise = new Promise(function(resolve, reject) {
    setTimeout(resolve, 2000, "promise1"); // изменяем состояние объекта на fulfilled (успешное выполнение) через 2 секунды
});

const promise2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 1000, "promise2"); // изменяем состояние объекта на fulfilled (успешное выполнение) через 1 секунду
});

const promise3 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, "promise3"); // изменяем состояние объекта на fulfilled (успешное выполнение) через пол секунды
});

Promise.all([promise, promise2, promise3]) // ожидаем выполнение всех переданных в массиве обещаний / или отклонения какого-то обещания
                .then(val => console.log(val)); // обработчик для успешного выполнения

// 

В этом примере мы инициализировали три переменные, которые содержат объект Promise, который изменяет состояние объекта на fulfilled (успешное выполнение) через 2 секунды в первом случае, через одну секунду во втором и через пол секунды в третьем.

С использованием метода .all() объекта Promise мы принимаем массив, который содержит ссылку на три обещания, результат выполнения этого метода зависит от того с каким результатом завершится выполнение обещаний, в нашем случае он возвращает объект Promise, который имеет состояние fulfilled (успешное выполнение), так как все переданные объекты Promise в аргументе имеют состояние fulfilled (успешное выполнение).

С использованием метода then() мы добавили обработчик, вызываемый когда объект Promise имеет состояние fulfilled (успешное выполнение), и выводим в консоль полученное значение (массив полученных значений из всех обещаний).

Далее мы с Вами рассмотрим пример в котором увидим, что произойдет, если одно из обещаний будет отклонено:

const promise = new Promise(function(resolve, reject) {
    setTimeout(resolve, 2000, "promise1"); // изменяем состояние объекта на fulfilled (успешное выполнение) через 2 секунды
});

const promise2 = new Promise(function(resolve, reject) {
    setTimeout(reject, 1000, new Error("Обещание отклонено")); // изменяем состояние объекта на rejected (выполнение отклонено) через 1 секунду
});

const promise3 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, "promise3"); // изменяем состояние объекта на fulfilled (успешное выполнение) через пол секунды
});

Promise.all([promise, promise2, promise3]) // ожидаем выполнение всех переданных в массиве обещаний / или отклонения какого-то обещания
                .then(val => console.log(val), // обработчик для успешного выполнения
                           err => console.log(err.message)); // обработчик для случая, когда выполнение отклонено

// Обещание отклонено

По аналогии с предыдущим примером мы инициализировали три переменные, которые содержат объект Promise, который изменяет состояние объекта на fulfilled (успешное выполнение) через 2 секунды в первой и через пол секунды в третьей переменной

Обратите внимание, что с помощью метода reject() мы изменяем значение объекта Promise на rejected (выполнение отклонено) через 1 секунду во второй переменной

С использованием метода .all() объекта Promise мы принимаем массив, который содержит ссылку на три обещания, результат выполнения этого метода зависит от того с каким результатом завершится выполнение обещаний, в нашем случае он возвращает объект Promise, который имеет состояние rejected (выполнение отклонено), это связано с тем, что один из переданных объектов изменил своё состояние на rejected (выполнение отклонено).

С использованием метода then() мы добавили обработчики, вызываемые когда объект Promise имеет состояние fulfilled (успешное выполнение), или rejected (выполнение отклонено). В нашем случае срабатывает обработчик для отклоненного выполнения, и выводит информацию об ошибке в консоль.

Нюансы использования метода

Promise.all()
                .then(val => console.log(val)); // обработчик для успешного выполнения

// 

Promise.all()
                .then(val => console.log(val)); // обработчик для успешного выполнения

// 

Promise.all([])
                .then(val => console.log(val)); // обработчик для успешного выполнения

// []            

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

JavaScript Promise

Promise How To

Here is how to use a Promise:

myPromise.then(
  function(value) { /* code if successful */ },
  function(error) { /* code if some error */ }
);

Promise.then() takes two arguments, a callback for success and another for failure.

Both are optional, so you can add a callback for success or failure only.

Example

function myDisplayer(some) {
  document.getElementById(«demo»).innerHTML = some;}
let myPromise = new Promise(function(myResolve, myReject) {
  let x = 0;
// The producing code (this may take some time)
  if (x == 0) {
    myResolve(«OK»);
  } else {
    myReject(«Error»);
  }
});
myPromise.then(
  function(value) {myDisplayer(value);},
  function(error) {myDisplayer(error);});

How to Avoid Callback Hell – PizzaHub Example

Let’s order a Veg Margherita pizza from the PizzaHub. When we place the order, PizzaHub automatically detects our location, finds a nearby pizza restaurant, and finds if the pizza we are asking for is available.

If it’s available, it detects what kind of beverages we get for free along with the pizza, and finally, it places the order.

If the order is placed successfully, we get a message with a confirmation.

So how do we code this using callback functions? I came up with something like this:

Let’s have a close look at the function in the above code.

It calls an API to get your nearby pizza shop’s id. After that, it gets the list of pizzas available in that restaurant. It checks if the pizza we are asking for is found and makes another API call to find the beverages for that pizza. Finally the order API places the order.

Here we use a callback for each of the API calls. This leads us to use another callback inside the previous, and so on.

This means we get into something we call (very expressively) . And who wants that? It also forms a code pyramid which is not only confusing but also error-prone.

Demonstration of callback hell and pyramid

There are a few ways to come out of (or not get into) . The most common one is by using a or function. However, to understand functions well, you need to have a fair understanding of s first.

So let’s get started and dive into promises.

Understanding Promise States

Just to review, a promise can be created with the constructor syntax, like this:

The constructor function takes a function as an argument. This function is called the .

The executor function takes two arguments, and . These are the callbacks provided by the JavaScript language. Your logic goes inside the executor function that runs automatically when a is created.

For the promise to be effective, the executor function should call either of the callback functions, or . We will learn more about this in detail in a while.

The constructor returns a object. As the executor function needs to handle async operations, the returned promise object should be capable of informing when the execution has been started, completed (resolved) or retuned with error (rejected).

A object has the following internal properties:

  1. – This property can have the following values:
  • : Initially when the executor function starts the execution.
  • : When the promise is resolved.
  • : When the promise is rejected.

Promise states

2.   – This property can have the following values:

  • : Initially when the value is .
  • : When is called.
  • : When is called.

These internal properties are code-inaccessible but they are inspectable. This means that we will be able to inspect the and property values using the debugger tool, but we will not be able to access them directly using the program.

Able to inspect the internal properties of a promise

A promise’s state can be , or . A promise that is either resolved or rejected is called .

A settled promise is either fulfilled or rejected

How promises are resolved and rejected

Here is an example of a promise that will be resolved ( state) with the value immediately.

The promise below will be rejected ( state) with the error message .

An important point to note:

In the example above, only the first one to resolve will be called and the rest will be ignored.

How to handle a Promise once you’ve created it

A uses an executor function to complete a task (mostly asynchronously). A consumer function (that uses an outcome of the promise) should get notified when the executor function is done with either resolving (success) or rejecting (error).

The handler methods, , and , help to create the link between the executor and the consumer functions so that they can be in sync when a promise s or s.

The executor and consumer functions

Концепция 2: Цепочка промисов

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

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

Цепочка вызовов может быть реализована двумя способами:

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

timer().then(successHandler1, failureHandler1).
.then(successHandler2, failureHandler2)
.then(successHandler3, failuerHandler3)
.then(successHandler4, failureHandler4)

Использование стандартного обработчика ошибок – обратный вызов failureHandlerне является обязательным в методе then. Поэтому можно использовать блок catch для общей обработки ошибок:

timer().then(successHandler1)
.then(successHandler2)
.then(successHandler3)
.catch(errorHandler)

В приведенном выше примере можно передать параметр через разные обратные вызовы и увеличивать их значение.

Если мы возвращаем error из любого обратного вызова then, то для последующих методов then обратный вызов error будет выполнен. После чего ошибка будет передана в последний блок catch.

Promise.any

Similar to , but waits only for the first fulfilled promise and gets its result. If all of the given promises are rejected, then the returned promise is rejected with – a special error object that stores all promise errors in its property.

The syntax is:

For instance, here the result will be :

The first promise here was fastest, but it was rejected, so the second promise became the result. After the first fulfilled promise “wins the race”, all further results are ignored.

Here’s an example when all promises fail:

As you can see, error objects for failed promises are available in the property of the object.

Что такое и для чего нужен Promise

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

Чтобы получить результат выполнения или ошибку (причину), вызвавшую отклонение, можно использовать метод then() объекта Promise:

promise.then(, )

Здесь onFulflled() – это функция, которой передается результат выполнения асинхронной операции, а onRejected() – функция, которой передается причина отклонения. Обе функции являются необязательными.

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

asyncOperation(arg, (err, result) => {
  if(err) {
    //обработка ошибки
  }
  //работа с результатом
});

Объекты Promise позволяют преобразовать этот типичный CPS-­код в более структурированный и элегантный код, например:

asyncOperation(arg)
  .then(result => {
    //работа с результатом
  }, err => {
    //обработка ошибки
  });

Одним из важнейших свойств метода then() является синхронный возврат другого объекта Promise. Если любая из функций – onFulflled() или onRejected() – вернет значение x, метод then() вернет один из следующих объектов Promise:

  • выполненный со значением x, если x является значением;
  • выполненный с объектом x, где x является объектом Promise или thenableобъектом;
  • отклоненный с причиной отклонения x, где x является объектом Promise или thenable­объектом.

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

asyncOperation(arg)
  .then(result1 => {
    //возвращает другой объект Promise
    return asyncOperation(arg2);
  })
  .then(result2 => {
    //возвращает значение
    return 'done';
  })
  .then(undefined, err => {
    // здесь обрабатываются все возникшие в цепочке ошибки
  });

Схема на рисунке иллюстрирует другую точку зрения на работу цепочки объектов Promise:

Другим важным свойством объектов Promise является гарантированный асинхронный вызов функций onFulflled() и onRejected(), даже при синхронном выполнении, как в предыдущем примере, где последняя функция then() в цепочке возвращает строку ‘done’. Такая модель поведения защищает код от непреднамеренного высвобождения Залго, что без дополнительных усилий делает асинхронный код более последовательным и надежным.

А теперь самое интересное: если в обработчике onFulflled() или onRejected() возбудить исключение (оператором throw), возвращаемый методом then() объект Promise автоматически будет отклонен с исключением в качестве причины отказа. Это огромное преимущество перед CPS, потому что исключение автоматически будет передаваться вдоль по цепочке, а это означает, что можно использовать оператор throw.

Исторически сложилось, что существует множество библиотек, реализующих объекты Promise, большинство которых не совместимо друг с другом, что препятствует созданию then­цепочек из объектов Promise, созданных разными библиотеками.

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

Promise.all

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

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

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

Синтаксис:

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

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

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

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

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

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

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

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

Например:

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

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

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

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

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

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

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

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

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

Consumers: then, catch, finally

A Promise object serves as a link between the executor (the “producing code” or “singer”) and the consuming functions (the “fans”), which will receive the result or error. Consuming functions can be registered (subscribed) using methods , and .

The most important, fundamental one is .

The syntax is:

The first argument of is a function that runs when the promise is resolved, and receives the result.

The second argument of is a function that runs when the promise is rejected, and receives the error.

For instance, here’s a reaction to a successfully resolved promise:

The first function was executed.

And in the case of a rejection, the second one:

If we’re interested only in successful completions, then we can provide only one function argument to :

If we’re interested only in errors, then we can use as the first argument: . Or we can use , which is exactly the same:

The call is a complete analog of , it’s just a shorthand.

Just like there’s a clause in a regular , there’s in promises.

The call is similar to in the sense that always runs when the promise is settled: be it resolve or reject.

is a good handler for performing cleanup, e.g. stopping our loading indicators, as they are not needed anymore, no matter what the outcome is.

Like this:

That said, isn’t exactly an alias of though. There are few subtle differences:

  1. A handler has no arguments. In we don’t know whether the promise is successful or not. That’s all right, as our task is usually to perform “general” finalizing procedures.

  2. A handler passes through results and errors to the next handler.

    For instance, here the result is passed through to :

    And here there’s an error in the promise, passed through to :

That’s very convenient, because is not meant to process a promise result. So it passes it through.

We’ll talk more about promise chaining and result-passing between handlers in the next chapter.

We can attach handlers to settled promises

If a promise is pending, handlers wait for it. Otherwise, if a promise has already settled, they just run:

Note that this makes promises more powerful than the real life “subscription list” scenario. If the singer has already released their song and then a person signs up on the subscription list, they probably won’t receive that song. Subscriptions in real life must be done prior to the event.

Promises are more flexible. We can add handlers any time: if the result is already there, they just execute.

Next, let’s see more practical examples of how promises can help us write asynchronous code.

Оркестровые промисы

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

Синтаксис ES2015 деструктуризации позволяет сделать так:

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

Это был пример с . Сейчас давай разберем с реальными данными.
Нам нужно 2 , с которых мы получим данные. Берём 2 учетные записи с github.

Так как мы получаем в качестве ответа поток данных, мы должны его преобразовать в читаемый . Ранее мы делали это с одним ответом через . Как это сделать если у нас два ? Мы перебираем с помощью , который вернет новый массив и на каждом вызываем второй промис .

Почему мы должны вызывать ?

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

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

Необработанные ошибки

Что произойдёт, если ошибка не будет обработана? Например, мы просто забыли добавить в конец цепочки, как здесь:

В случае ошибки выполнение должно перейти к ближайшему обработчику ошибок. Но в примере выше нет никакого обработчика. Поэтому ошибка как бы «застревает», её некому обработать.

На практике, как и при обычных необработанных ошибках в коде, это означает, что что-то пошло сильно не так.

Что происходит, когда обычная ошибка не перехвачена ? Скрипт умирает с сообщением в консоли. Похожее происходит и в случае необработанной ошибки промиса.

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

В браузере мы можем поймать такие ошибки, используя событие :

Это событие является частью .

Если происходит ошибка, и отсутствует её обработчик, то генерируется событие , и соответствующий объект содержит информацию об ошибке.

Обычно такие ошибки неустранимы, поэтому лучше всего – информировать пользователя о проблеме и, возможно, отправить информацию об ошибке на сервер.

В не-браузерных средах, таких как Node.js, есть другие способы отслеживания необработанных ошибок.

Реализации 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).

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):

Сложный кейс с промисами, и главное преимущество промисов — колбеки

Попалась мне интереснейшая задача «Платежная система отвечает об успешной оплате не сразу, поэтому придется слать несколько запросов в течение 30 секунд, при этом держать пользователя в режиме прелоадера, при этом если оплата пройдет раньше чем 30 секунд, то из цикла нужно выйти и отключить прелоадер, и если за 30 секунд ответа не получено, то показать ошибку».

Архитектура:

  1. Интервал запросов к серверу 1 секунда.
  2. Необходим один большой (глобальный) промис, чтобы было удобно отключить прелоадер.
  3. До входа в асинхронный код нужно включить прелоадер.
  4. Внутри асинхронного кода должно произойти «нечто ужасное» без потери читабельности.
  5. Отключение прелоадера должно происходить в финальном коде, независимо от того, успешная оплата, или ошибка, и независимо от того, сколько промисов будет использоваться в асинхронном коде.

Для удобства сопоставления алгоритма и архитектуры код совсем чуть-чуть упрощен, и совпадает с оригинальным на 90%.

Результат:

На данном примере видно как используются колбэки — у нас есть полный простор в передаче ошибок в родительский промис, их множественный вызов в разных местах, когда нам необходимо. И также максимальный простор для выбора момента уведомления «родителя» об успешном окончании, тем самым мы решаем 5 пункт из запланированной архитектуры.

Пример: loadScript

Давайте используем эту возможность вместе с промисифицированной функцией , созданной нами в , чтобы загружать скрипты по очереди, последовательно:

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

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

Мы можем добавить и другие асинхронные действия в цепочку

Обратите внимание, что наш код всё ещё «плоский», он «растёт» вниз, а не вправо. Нет никаких признаков «адской пирамиды вызовов»

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

Этот код делает то же самое: последовательно загружает 3 скрипта. Но он «растёт вправо», так что возникает такая же проблема, как и с колбэками.

Разработчики, которые не так давно начали использовать промисы, иногда не знают про цепочки и пишут код именно так, как показано выше. В целом, использование цепочек промисов предпочтительнее.

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

Thenable

Если быть более точными, обработчик может возвращать не именно промис, а любой объект, содержащий метод , такие объекты называют «thenable», и этот объект будет обработан как промис.

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

Вот пример такого объекта:

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

Это позволяет добавлять в цепочки промисов пользовательские объекты, не заставляя их наследовать от .

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

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

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

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

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

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

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

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

Нормальная функция против асинхронной.

Давайте посмотрим на эти два примера, каждый пример делает сложение двух чисел, один пример с нормальной функцией, другой с удаленной.

Нормальная функция для сложения чисел.

Асинхронная функция для сложения двух чисел:

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

Или таким способом вы вообще не можете знать — получите ли вы результат, потому что сервер может просто упасть, тормознуть с ответом и т. п. Вам не нужно, чтобы весь процесс был заблокирован, пока вы ждете результат.

Вызов API, скачивание файлов, чтение файлов — всё это те обычные async операции, которые вы можете выполнять.

Promise: Последовательные итерации

На данный момент, на примере приложения веб­-паука, мы рассмотрели объекты Promise и приемы их использования для создания простой элегантной реализации последовательного потока выполнения. Но этот код обеспечивает выполнение лишь известного заранее набора асинхронных операций. Поэтому, чтобы восполнить пробелы в исследовании последовательного выполнения, нам нужно разработать фрагмент, реализующий итерации с помощью объектов Promise. И снова прекрасным примером для демонстрации станет функция spiderLinks().

function spiderLinks(currentUrl, body, nesting) {
  let promise = Promise.resolve();
  if(nesting === 0) {
    return promise;
  }
  const links = utilities.getPageLinks(currentUrl, body);
  links.forEach(link => {
    promise = promise.then(() => spider(link, nesting – 1));
  });
  return promise;
}

Для асинхронного обхода всех ссылок на веб-­странице нужно динамически создать цепочку объектов Promise.

  1. Начнем с определения «пустого» объекта Promise, разрешаемого как undefned. Он будет служить началом цепочки.
  2. Затем в цикле присвоим переменной promise новый объект Promise, полученный вызовом метода then() предыдущего объекта Promise в цепочке. Это и есть шаблон асинхронных итераций с использованием объектов Promise.

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

Итого

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

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

Adblock
detector