Познаем промисы на основе ecmascript спецификации. знакомство

Promise reaction как последствие исполненного обещания

Как вы могли заметить на рисунке 14, последствия разрешения / резолвинга promise объекта подписаны как «+реакция» и «-реакция». Официальный термин для этих слов из ECMAScript спецификации — promise reaction. Предполагается, что в следующих статьях эта тема будет рассмотрена подробно. Пока что ограничимся общим представлением того, что такое promise reaction, чтобы можно было правильно ассоциировать этот термин с философским смыслом этого слова и его техническим исполнением.

Как мы помним, у обещания могут быть последствия, а могут и не быть. Что же такое последствие? Это действие, которое произойдет некоторым временем позже: после того как обещание исполнится. А раз это действие, то последствие может быть выражено обычной JavaScript функцией. Одни функции исполнятся в случае успешного резолвинга промиса (+реакция); другие функции — в случае, когда промис перейдет в состояние rejected (-реакция). Технически эти функции (последствия) передаются аргументами при вызове метода Promise.prototype.then().

Таким образом важной частью promise reaction является асинхронное действие, выполняемое когда-то в будущем. Есть и вторая важная составляющая часть promise reaction — это новосозданный промис, возвращаемый после выполнения команды Promise.prototype.then()

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

По факту promise reaction связывает обещания между собой в каком-то временном интервале

Важно помнить тот момент, что реакции обрабатываются в автоматическом режиме. Вызовы функций — последствий разрешения промиса — осуществляются движком JS, не программистом (рис. 18)

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

рис 18. ( Последствия разрешения обещания записываются callback функциями в методе then(). Callback будет вызван асинхронно автоматически движком JS )

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 в цепочке.

Пример с setTimeout

Возьмём в качестве асинхронной операции, которая должна через некоторое время успешно завершиться с результатом «result»:

В результате запуска кода выше – через 1 секунду выведется «Fulfilled: result».

А если бы вместо был вызов , то вывелось бы «Rejected: error». Впрочем, как правило, если при выполнении возникла проблема, то вызывают не со строкой, а с объектом ошибки типа :

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

Только один аргумент

Функции принимают ровно один аргумент – результат/ошибку.

Именно он передаётся обработчикам в , как можно видеть в примерах выше.

Promise.resolve/reject

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

Мы рассмотрим их здесь для полноты картины, а также для тех, кто по каким-то причинам не может использовать .

Promise.resolve(value) создаёт успешно выполненный промис с результатом value.

То же самое, что:

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

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

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

Promise.reject(error) создаёт промис, завершённый с ошибкой error.

То же самое, что:

На практике этот метод почти никогда не используется.

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

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

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.all

Let’s say we want many promises to execute in parallel and wait until all of them are ready.

For instance, download several URLs in parallel and process the content once they are all done.

That’s what is for.

The syntax is:

takes an array of promises (it technically can be any iterable, but is usually an array) and returns a new promise.

The new promise resolves when all listed promises are settled, and the array of their results becomes its result.

For instance, the below settles after 3 seconds, and then its result is an array :

Please note that the order of the resulting array members is the same as in its source promises. Even though the first promise takes the longest time to resolve, it’s still first in the array of results.

A common trick is to map an array of job data into an array of promises, and then wrap that into .

For instance, if we have an array of URLs, we can fetch them all like this:

A bigger example with fetching user information for an array of GitHub users by their names (we could fetch an array of goods by their ids, the logic is identical):

If any of the promises is rejected, the promise returned by immediately rejects with that error.

For instance:

Here the second promise rejects in two seconds. That leads to an immediate rejection of , so executes: the rejection error becomes the outcome of the entire .

In case of an error, other promises are ignored

If one promise rejects, immediately rejects, completely forgetting about the other ones in the list. Their results are ignored.

For example, if there are multiple calls, like in the example above, and one fails, the others will still continue to execute, but won’t watch them anymore. They will probably settle, but their results will be ignored.

does nothing to cancel them, as there’s no concept of “cancellation” in promises. In another chapter we’ll cover that can help with that, but it’s not a part of the Promise API.

allows non-promise “regular” values in

Normally, accepts an iterable (in most cases an array) of promises. But if any of those objects is not a promise, it’s passed to the resulting array “as is”.

For instance, here the results are :

So we are able to pass ready values to where convenient.

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 — обещать. Небольшое уточнение, пожалуйста, не усложняйте понимание другим с произношением, так как во всём русскоязычном мире принято говорить “промис” в случае с JavaScript.

Итак, у промиса есть 3 состояния. Это:

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

2. Промис решен (). Вам реально купят новый телефон.

3. Промис отклонен (). Вы не получили новый мобильный телефон, так как всё-таки, мама была не в настроении.

Перевод функций в стиле 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 с созданным списком аргументов.

JavaScript Promise Object

A JavaScript Promise object contains both the producing code and calls to the consuming code:

Promise Syntax

let myPromise = new Promise(function(myResolve, myReject) {
// «Producing Code» (May take some time)
  myResolve(); // when successful
  myReject();  // when error
});
// «Consuming Code» (Must wait for a fulfilled Promise)
myPromise.then(
  function(value) { /* code if successful */ },
  function(error) { /* code if some error */ }
);

When the executing code obtains the result, it should call one of the two callbacks:

Result Call
Success myResolve(result value)
Error myReject(error object)

Объект promise: его философия, техническое представление, возможные состояния

Уже не раз было мною замечено, что качественное обучение программированию должно состоять из 2 частей. Это — философское осмысление идеи, а уже затем её техническая реализация. То есть обычная человеческая логика, которой ученик руководствуется при принятии каких-либо решений, сильно облегчает понимание технической реализации этого решения. Поэтому начнем мы с того, что такое обещание в жизни, и как мы к нему относимся? А затем посмотрим: как примеры обещаний будут реализованы в коде. Рассмотрим следующие рисунки (рис. 1, 2, 3).

рис 1. ( `PromiseState` — как результат обещания )рис 2. ( `PromiseResult` — как информация, связанная с результатом выполненного или невыполненного обещания )рис 3. ( `PromiseFulfillReactions`, `PromiseRejectReactions` — как последствия, которые наступают после выполнения или невыполнения обещания )

Мы видим, что само понятие обещания стоит на 3-х китах. 1) Было ли выполнено обещание вообще? 2) Какую дополнительную информацию мы можем извлечь после выполнения или отказа обещания? 3) Какие последствия наступят в случае положительного или отрицательного исхода нашего обещания?

Технически же обещание — это обыкновенная сущность, выраженная через такой тип данных, как объект. У этой сущности есть имя / класс Promise. Объекты, рожденные от этого класса, имеют в цепочке своих прототипов Promise.prototype. И данная сущность должна быть как-то связана со всей той «информацией из жизни», которую мы рассмотрели выше. Спецификация ECMAScript эту информацию закладывает в промис еще на уровне, который ниже по абстракции, чем сам JavaScript. Например, на уровень С++. Соответственно в объекте промиса есть место и под статус, и под результат, и под последствия обещания. Взгляните на то, из чего состоит обещание по  (рис. 4).

рис 4. ( Внутренние поля promise объекта по версии ECMAScript спецификации )

Какими новыми красками заиграла фраза «обещать — не значит жениться» с точки зрения программиста? 1) `PromiseState`. Кто-то не женился. 2) `PromiseResult`. Потому что ему не хватило денег на свадьбу. 3) `PromiseRejectReactions`. Как следствие — у него появилось куча свободного времени, которое он потратил на саморазвитие 4) `PromiseFulfillReactions`. А зачем человеку план B, когда он уже выбрал план A?

Да, есть и пятое поле `PromiseIsHandled`

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

Если нет, то необработанный reject обещания интерпретируется движком JS как ошибка. Для нетерпеливых: если программист не повесил вторым аргументом через Promise.prototype.then() функцию-callback-обработчик reject-а состояния промиса — то возникшее состояние «rejected» promise объекта покажет вам красную ошибку в консоли разработчика.

Обратили внимание, что поля объекта promise заключены в «]»? Этим подчеркивается, что доступ к данной информации у программиста JS напрямую не имеется. Только через специальные средства / команды / API, как, например, команду Promise.prototype.then()

Если же у вас есть непреодолимое желание управлять «этой кухней» напрямую, то добро пожаловать в клуб разработчиков стандартов спецификации EcmaScript.

Небольшое замечание в конце данной подглавы. Если в жизни у нас могут быть обещания выполнены частично, то в EcmaScript — нет. То есть, если человек обещал отдать миллион, а отдал 950 тысяч, то в жизни, может, он и надежный партнер, но для для JavaScript такой должник будет занесен в черный список через `PromiseState` === «rejected». Promise объект меняет свое состояние однозначно и всего лишь один раз. Как это технически реализуется — немного позже.

Chaining Asynchronous Functions

There are two ways of chaining asynchronous function calls. The first one is to make the callback return a promise object and to chain calls. Indeed, returns a that is resolved when the callback resolves its promise.

Example:

function late(n) {
    var p = new promise.Promise();
    setTimeout(function() {
        p.done(null, n);
    }, n);
    return p;
}

late(100).then(
    function(err, n) {
        return late(n + 200);
    }
).then(
    function(err, n) {
        return late(n + 300);
    }
).then(
    function(err, n) {
        return late(n + 400);
    }
).then(
    function(err, n) {
        alert(n);
    }
);

The other option is to use . The function expects an array of asynchronous functions that return a promise each. itself returns a .

promise.chain(f1, f2, f3, ...);

Example:

function late(n) {
    var p = new promise.Promise();
    setTimeout(function() {
        p.done(null, n);
    }, n);
    return p;
}

promise.chain(
    function() {
        return late(100);
    },
    function(err, n) {
        return late(n + 200);
    },
    function(err, n) {
        return late(n + 300);
    },
    function(err, n) {
        return late(n + 400);
    }
).then(
    function(err, n) {
        alert(n);
    }
);

Using the Promise Object

Promises provide an alternative to callback-passing. Asynchronous functions return a object onto which callbacks can be attached.

Callbacks are attached using the method. They will be called when the promise is resolved.

var p = asyncfoo(a, b, c);

p.then(function(error, result) {
    if (error) return;
    alert(result);
});

Asynchronous functions must resolve the promise with the method when their task is done. This invokes the promise callback(s) with the same arguments that were passed to .

function asyncfoo() {

    var p = new promise.Promise();  /* (1) create a Promise */

    setTimeout(function() {
        p.done(null, "O hai!");     /* (3) resolve it when ready */
    }, 1000);

    return p;                       /* (2) return it */
}

Summary

There are 6 static methods of class:

  1. – waits for all promises to resolve and returns an array of their results. If any of the given promises rejects, it becomes the error of , and all other results are ignored.
  2. (recently added method) – waits for all promises to settle and returns their results as an array of objects with:

    • : or
    • (if fulfilled) or (if rejected).
  3. – waits for the first promise to settle, and its result/error becomes the outcome.
  4. (recently added method) – waits for the first promise to fulfill, and its result becomes the outcome. If all of the given promises are rejected, becomes the error of .
  5. – makes a resolved promise with the given value.
  6. – makes a rejected promise with the given error.

Of all these, is probably the most common in practice.

JavaScript Promise Chaining

Promises are useful when you have to handle more than one asynchronous task, one after another. For that, we use promise chaining.

You can perform an operation after a promise is resolved using methods , and .

The method is used with the callback when the promise is successfully fulfilled or resolved.

The syntax of method is:

Example 2: Chaining the Promise with then()

Output

Promise resolved
You can call multiple functions this way.

In the above program, the method is used to chain the functions to the promise. The method is called when the promise is resolved successfully.

You can chain multiple methods with the promise.

JavaScript catch() method

The method is used with the callback when the promise is rejected or if an error occurs. For example,

Output

Promise rejected

In the above program, the promise is rejected. And the method is used with a promise to handle the error.

Working of JavaScript promise chaining

Популярные ошибки и подводные камни

Из-за сложных манипуляций с промисами и async/await концепциями вы можете встретиться с различными тонкостями, что может привести к ошибкам.

Не забывайте await

Частая ошибка заключается в том, что перед промисом забывается ключевое слово :

Обратите внимание, здесь не используется ни , ни. Функция всегда будет завершаться с (без задержки в 1 секунду)

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

async-функции в обратных вызовах

async-функции часто используются в . или . в качестве коллбэков. Вот пример — допустим, существует функция , которая возвращает количество открытых репозиториев на GitHub. Есть 3 пользователя, чьи показатели нужно взять. Используется такой код:

И для того, чтобы получить количество репозиториев пользователей (), код должен выглядеть как-то так:

Обратите внимание на слово в обратном вызове функции. Можно было бы ожидать, что переменная будет содержать число — количество репозиториев

Но как было сказано ранее, все async-функции возвращают промисы. Следовательно, будет массивом промисов. вызывает анонимной коллбэк для каждого пользователя.

Слишком последовательное использование await

Допустим, есть такой код:

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

Если на обработку одного пользователя будет уходить 300 мс, то на всех пользователей уйдёт почти секунда. В этом случае затрачиваемое время будет линейно зависеть от количества пользователей. Поскольку получение количества репозиториев не зависит друг от друга, то можно распараллелить эти процессы. Тогда пользователи будут обрабатываться одновременно, а не последовательно. Для этого понадобятся и .

на входе получает массив промисов и возвращает промис. Возвращаемый промис завершается после окончания всех промисов в массиве либо при первом реджекте. Возможно, все эти промисы не запустятся строго одновременно. Чтобы добиться строгого параллелизма, взгляните на p-map. А если нужно, чтобы async-функции были более адаптивными, посмотрите на Async Iterators.

Перевод статьи «Deeply Understanding JavaScript Async and Await with Examples»

Промисы в 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 операции, которые вы можете выполнять.

Пример: loadScript

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

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

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

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

Применение:

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

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

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

Пробрасывание ошибок

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

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

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

В примере ниже успешно обрабатывает ошибку:

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

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

Управление переходит от первого блока к следующему , вниз по цепочке.

Как создаются промисы в JavaScript?

Синтаксис объявления обещания в JavaScript выглядит следующим образом:

У конструктора Promise() имеются два параметра — resolve и reject . Они являются функциями, в которые можно передать любые объекты. В случае успешного выполнения операции вызывается функция resolve(). При возникновении ошибки или исключительной ситуации вызывается функция reject(). Условие когда вызывается та или иная функция определяется определяет сам программист.

JavaScript Promise имеет три состояния:

  • pending — ожидание ответа. Когда операция еще не выполнена и не завершена с ошибкой (отклонена)
  • resolved — операция выполнена и было присвоено значение
  • rejected — отклонено или выполнено с ошибкой

В качестве примера можно вывести в консоль обещание, которое мы определили выше:

Получим следующее:

В следствии того что наш промис пустой и не выполняет ничего полезного, он не разрешился и находится в статусе pending со значением undefined.

Если внутри промиса будут вызваны методы resolve() или reject(), то он завершится со статусами resolved и rejected соответственно.

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.

AJAX Functions Included

Because AJAX requests are the root of much asynchrony in Javascript, promise.js provides the following functions:

promise.get(url, data, headers)
promise.post(url, data, headers)
promise.put(url, data, headers)
promise.del(url, data, headers)

(optional) : a {key: value} object or url-encoded string.

(optional) : a {key: value} object (e.g. ).

Example:

promise.get('/').then(function(error, text, xhr) {
    if (error) {
        alert('Error ' + xhr.status);
        return;
    }
    alert('The page contains ' + text.length + ' character(s).');
});

You can set a time in milliseconds after which unresponsive AJAX
requests should be aborted. This is a global configuration option,
disabled by default.

Итого

Мы ознакомились с пятью статическими методами класса :

  1. – ожидает выполнения всех промисов и возвращает массив с результатами. Если любой из указанных промисов вернёт ошибку, то результатом работы будет эта ошибка, результаты остальных промисов будут игнорироваться.
  2. (добавлен недавно) – ждёт, пока все промисы завершатся и возвращает их результаты в виде массива с объектами, у каждого объекта два свойства:

    • : , если выполнен успешно или , если ошибка,
    • – результат, если успешно или – ошибка, если нет.
  3. – ожидает первый выполненный промис, который становится его результатом, остальные игнорируются.
  4. – возвращает успешно выполнившийся промис с результатом .
  5. – возвращает промис с ошибкой .

Из всех перечисленных методов, самый часто используемый – это, пожалуй, .

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

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

Adblock
detector