Ecmascript 6 promises
Содержание:
- Promise.resolve/reject
- Концепция 4: Async и await
- Очередь микрозадач
- Потребители: then, catch, finally
- Пробрасывание ошибок
- Конструктор Promise, его философия. Callback функция executor — как «выполнитель» обещания. Схема взаимодействия: Promise ( конструктор ) — executor ( callback ) — promise ( объект )
- Необработанные ошибки
- Understanding JavaScript Promises
- Promise: Последовательные итерации
- Выполнение промиса
- Bigger example: fetch
- Promises States In Javascript
- Сложный кейс с промисами, и главное преимущество промисов — колбеки
- Пример использования
- Summary
- JavaScript Promise Methods
- Promise.allSettled
- Javascript Promise
- Перехват ошибок
- Promise reaction как последствие исполненного обещания
- Creating a promise: the Promise constructor
Promise.resolve/reject
Методы и редко используются в современном коде, так как синтаксис (мы рассмотрим его чуть позже) делает его, в общем-то, не нужным.
Мы рассмотрим их здесь для полноты картины, а также для тех, кто по каким-то причинам не может использовать .
Promise.resolve(value) создаёт успешно выполненный промис с результатом value.
То же самое, что:
Этот метод используют для совместимости: когда ожидается, что функция возвратит именно промис.
Например, функция ниже загружает URL и запоминает (кеширует) его содержимое. При будущих вызовах с тем же URL он тут же читает предыдущее содержимое из кеша, но использует , чтобы сделать из него промис, для того, чтобы возвращаемое значение всегда было промисом:
Мы можем писать , потому что функция всегда возвращает промис. Мы всегда можем использовать после . Это и есть цель использования в строке .
Promise.reject(error) создаёт промис, завершённый с ошибкой error.
То же самое, что:
На практике этот метод почти никогда не используется.
Концепция 4: Async и await
Async-await позволяет вызывать асинхронные функции синхронно. Рассмотрим этот момент более подробно.
Асинхронные функции – это функции, которые переходят в состояние ожидания при вызове, а разрешение зависит от результата выполнения.
Предположим, что нужно обработать две или более подобных функций. После этого результат первой функции необходимо передать второй функции. Для этого можно использовать вторую функцию внутри обратного вызова then первой функции. Но это сделает код более сложным для понимания. Здесь нам на помощь приходит концепция async-await.
Предположим, что у нас есть две асинхронные функции:
- getManagerByEmployeeId: принимает employeeId в качестве входных параметров и возвращает managerId.
- getManagerNameById:принимает в качестве входных параметров managerId и возвращает имя менеджера.
Наша задача – получить имя менеджера для данного идентификатора сотрудника. То есть, реализовать функцию getManagerName в приведенном ниже коде.
const EmployeeIDManagerIdMap = { "AA234": "0AA316", "BBCD5":"4AA354" };const ManagerIdManagerNameMap = { "0AA316":"John Doe", "4AA354":"Ravindram S" };function getManagerByEmployeeId(employeeId) { return new Promise((resolve, reject)=> { setTimeout(()=>{ if(EmployeeIdManagerIdMap) { resolve(EmployeeIdManagerIdMap); } else { reject(`Invalid employee id ${employeeId}`); } },2000); }); }function getManagerNameById(managerId) { return new Promise((resolve, reject)=> { setTimeout(()=>{ if(ManagerIdManagerNameMap) { resolve(ManagerIdManagerNameMap); } else { reject(`Invalid manager id ${managerId}`); } },2000); }); }// получаем имя менеджера по employeeId function getManagerName(employeeId) { // возвращаем имя менеджера }
Один из способов сделать это – использовать структуру вложенных промисов и преобразовать getManagerName в промис.
function getManagerName(employeeId){ return new Promise((resolve, reject)=>{ getManagerByEmployeeId(employeeId).then(function(managerId){ getManagerNameById(managerId).then(function(managerName){ resolve(managerName); }, function(error){ reject(error); }) }, function(error){ reject(error); }) }) }
Это создаст структуру вложенного кода, которая сложна для понимания и заставляет использовать промис в функции getManagerName.
Теперь рассмотрим реализацию концепции async-await.
async function getManagerName(employeeId){ try { let managerId = await getManagerByEmployeeId(employeeId); try { let managerName = await getManagerNameById(managerId); return managerName; } catch(error) { console.error("getManagerNameById promise rejected", error); } } catch(error){ console.error("getManagerByEmployeeId promise rejected", error); } }
Благодаря ей код стал проще и нам не нужно использовать промис в функции getManagerName.
Конструкция await откладывает выполнение кода до тех пор, пока не будет разрешена вызываемая асинхронная функция.
Каждая функция, содержащая await, должна быть объявлена асинхронной с использованием ключевого слова async.
Но отклоненный промис выдает ошибку при вызове функции. Поэтому его нужно обработать с помощью блока try-catch, окружающего вызов асинхронной функции.
Очередь микрозадач
Асинхронные задачи требуют правильного управления. Для этого стандарт предусматривает внутреннюю очередь , более известную как «очередь микрозадач (microtask queue)» (термин V8).
Как сказано в :
- Очередь определяется как первым-пришёл-первым-ушёл (FIFO): задачи, попавшие в очередь первыми, выполняются тоже первыми.
- Выполнение задачи происходит только в том случае, если ничего больше не запущено.
Или, проще говоря, когда промис выполнен, его обработчики попадают в очередь. Они пока не выполняются. Движок JavaScript берёт задачу из очереди и выполняет её, когда он освободится от выполнения текущего кода.
Вот почему сообщение «код выполнен» в примере выше будет показано первым.
Обработчики промисов всегда проходят через эту внутреннюю очередь.
Если есть цепочка с несколькими , то каждый из них выполняется асинхронно. То есть сначала ставится в очередь, а потом выполняется, когда выполнение текущего кода завершено и добавленные ранее в очередь обработчики выполнены.
Но что если порядок имеет значение для нас? Как мы можем вывести после ?
Легко, используя :
Теперь порядок стал таким, как было задумано.
Потребители: then, catch, finally
Объект служит связующим звеном между исполнителем («создающим» кодом или «певцом») и функциями-потребителями («фанатами»), которые получат либо результат, либо ошибку. Функции-потребители могут быть зарегистрированы (подписаны) с помощью методов , и .
Наиболее важный и фундаментальный метод – .
Синтаксис:
Первый аргумент метода – функция, которая выполняется, когда промис переходит в состояние «выполнен успешно», и получает результат.
Второй аргумент – функция, которая выполняется, когда промис переходит в состояние «выполнен с ошибкой», и получает ошибку.
Например, вот реакция на успешно выполненный промис:
Выполнилась первая функция.
А в случае ошибки в промисе – выполнится вторая:
Если мы заинтересованы только в результате успешного выполнения задачи, то в можно передать только одну функцию:
Если мы хотели бы только обработать ошибку, то можно использовать в качестве первого аргумента: . Или можно воспользоваться методом , который сделает тоже самое:
Вызов – это сокращённый, «укороченный» вариант .
По аналогии с блоком из обычного , у промисов также есть метод .
Вызов похож на , в том смысле, что выполнится в любом случае, когда промис завершится: успешно или с ошибкой.
хорошо подходит для очистки, например остановки индикатора загрузки, его ведь нужно остановить вне зависимости от результата.
Например:
Но это не совсем псевдоним , как можно было подумать. Существует несколько важных отличий:
-
Обработчик, вызываемый из , не имеет аргументов. В мы не знаем, как был завершён промис. И это нормально, потому что обычно наша задача – выполнить «общие» завершающие процедуры.
-
Обработчик «пропускает» результат или ошибку дальше, к последующим обработчикам.
Например, здесь результат проходит через к :
А здесь ошибка из промиса проходит через к :
Это очень удобно, потому что не предназначен для обработки результата промиса. Так что он просто пропускает его через себя дальше.
Мы более подробно поговорим о создании цепочек промисов и передаче результатов между обработчиками в следующей главе.
-
Последнее, но не менее значимое: вызов удобнее, чем – не надо дублировать функции f.
На завершённых промисах обработчики запускаются сразу
Если промис в состоянии ожидания, обработчики в будут ждать его. Однако, если промис уже завершён, то обработчики выполнятся сразу:
Теперь рассмотрим несколько практических примеров того, как промисы могут облегчить нам написание асинхронного кода.
Пробрасывание ошибок
Как мы уже заметили, ведёт себя как . Мы можем иметь столько обработчиков , сколько мы хотим, и затем использовать один в конце, чтобы перехватить ошибки из всех обработчиков.
В обычном мы можем проанализировать ошибку и повторно пробросить дальше, если не можем её обработать. То же самое возможно для промисов.
Если мы пробросим () ошибку внутри блока , то управление перейдёт к следующему ближайшему обработчику ошибок. А если мы обработаем ошибку и завершим работу обработчика нормально, то продолжит работу ближайший успешный обработчик .
В примере ниже успешно обрабатывает ошибку:
Здесь блок завершается нормально. Поэтому вызывается следующий успешный обработчик .
В примере ниже мы видим другую ситуацию с блоком . Обработчик перехватывает ошибку и не может обработать её (например, он знает как обработать только ), поэтому ошибка пробрасывается далее:
Управление переходит от первого блока к следующему , вниз по цепочке.
Конструктор Promise, его философия. Callback функция executor — как «выполнитель» обещания. Схема взаимодействия: Promise ( конструктор ) — executor ( callback ) — promise ( объект )
Итак, мы выяснили, что promise — это сущность, которая технически представляет собой JS объект с особыми скрытыми внутренними полями, которые в свою очередь обеспечивают философское наполнение смыслом слова «обещание».
Когда новичок первый раз создает объект promise, то его ожидает следующая картина (рис. 5).
рис 5. ( Самый первый раз интуитивно создаем promise объект )
Что пошло не так, и почему ошибка — стандартный вопрос. При ответе на него лучше снова привести какую-то аналогию из жизни. Например, мало кто любит «пустозвонов» вокруг нас: которые только обещают, но ничего не делают по выполнению своих заявлений (политика не в счет). Мы намного лучше относимся к тем людям, которые после своего обещания имеют план и предпринимают сразу же какие-то действия для достижения обещанного результата.
Так и философия ECMAScript подразумевает, что если вы создаете обещание, то сразу же укажите, как вы его будете выполнять. Свой план действий программисту необходимо оформить в виде параметра-функции, которую передадите в конструктор Promise. Следующий эксперимент выглядит так (рис. 6).
рис 6. ( Создаем promise объект, передавая в конструктор Promise функцию executor )
Из подписи к рисунку мы видим, что функция (параметр конструктора Promise) имеет собственное название — executor. Её задача — начать выполнение обещания и, желательно, привести его к какому-то логическому завершению. И если программист может писать какой угодно код в executor-е, то как программисту просигнализировать JS-у, что все — работа сделана — можно идти и смотреть результаты обещания?
Маркеры или сигналы, которые помогут программисту сообщить, что обещание завершено, передаются автоматически в параметры executor-a в виде аргументов, специально сформированных JavaScript-ом. Эти параметры можно называть как угодно, но чаще всего вы встретите их под такими именами, как res и rej. В спецификации ECMAScript их полное название — resolve function и reject function. Эти маркеры-функции имеют свои особенности, которые рассмотрим чуть ниже.
Для осознания новой информации новичку предлагается самостоятельно закодировать следующее утверждение: «Обещаю, что смогу разделить одно число на другое и выдать ответ, если только делитель не ноль». Вот как будет выглядеть приблизительно такой код (рис. 7).
рис 7. ( Решение задачи на деление 2-х чисел через промисы )
Теперь можно проанализировать полученный результат. Мы видим, что уже второй раз консоль браузера показывает объект промис в интересном виде. А именно: указаны 2 дополнительных поля в двойных квадратных скобках. Можно спокойно провести аналогию между `PromiseState` и `PromiseStatus`, fulfilled и resolved, `PromiseValue` и `PromiseResult`. Да, браузер сам пытается подсказать программисту о наличии и значении внутренних полей promise объекта. Также мы видим воедино связанную систему объекта promise, функции executor, специальных функций-callback-маркеров res и rej.
Чтобы ученик / напарник раскрепостился еще больше в этом материале, ему предлагается следующий код (рис. 8). Необходимо его проанализировать и ответить на следующие вопросы.
рис 8. ( Вариация решения задачи на деление 2-х чисел через промисы )
Отработает ли код? Где здесь функция executor и какое она имеет имя? Подходящее ли в этом коде название «wantToDivide»? Что возвращает после себя функция bind? Почему в функцию bind аргументы передаются только на втором и третьем месте? Куда исчезли специальные функции resolve function и reject function? Каким образом необходимые вводные данные number1 и number2 попали в «план выполнения обещания»? Сколько элементов в псевдомассиве «arguments»? Можно ли по памяти восстановить то, как будет выглядеть ответ в консоли браузера?
Читателю предлагается самому подумать над ответами на вопросы. А также
поэкспериментировать в коде. Благо код небольшой и сама идея задачи — простая. Да, тут есть вопросы как на промисы, так и на общие знания JavaScript. Что поделать, везде нас поджидают неожиданности, которые не дают нам расслабиться. Как только вам станет все понятно — можно двигаться дальше.
Необработанные ошибки
Помните «необработанные ошибки» из главы Промисы: обработка ошибок?
Теперь мы можем описать, как именно JavaScript понимает, что ошибка не обработана.
«Необработанная ошибка» возникает в случае, если ошибка промиса не обрабатывается в конце очереди микрозадач.
Обычно, если мы ожидаем ошибку, мы добавляем в конец цепочки промисов, чтобы обработать её:
…Но если мы забудем добавить , то, когда очередь микрозадач опустеет, движок сгенерирует событие:
А что, если мы поймаем ошибку, но позже? Вот так:
Теперь, при запуске, мы сначала увидим «Ошибка в промисе!», а затем «поймана».
Если бы мы не знали про очередь микрозадач, то могли бы удивиться: «Почему сработал обработчик ? Мы же поймали ошибку!».
Но теперь мы понимаем, что событие возникает, когда очередь микрозадач завершена: движок проверяет все промисы и, если какой-либо из них в состоянии «rejected», то генерируется это событие.
В примере выше , добавленный в , также срабатывает, но позже, уже после возникновения , так что это ни на что не влияет.
Understanding JavaScript Promises
In JavaScript, a promise is an object that returns a value which you hope to receive in the future, but not now.
Because the value will be returned by the promise in the future, the promise is very well-suited for handling asynchronous operations.
It’ll be easier to understand the concept of JavaScript promises through an analogy.
Suppose that you promise to complete learning JavaScript by next month.
You don’t know if you will spend your time and effort to learn JavaScript until next month. You can either be completing learning JavaScript or not.
A promise has three states:
- Pending: you don’t know if you will complete learning JavaScript by the next month.
- Fulfilled: you complete learning JavaScript by the next month.
- Rejected: you don’t learn JavaScript at all.
A promise starts in the pending state which indicates that the promise hasn’t completed. It ends with either fulfilled (successful) or rejected (failed) state.
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.
- Начнем с определения «пустого» объекта Promise, разрешаемого как undefned. Он будет служить началом цепочки.
- Затем в цикле присвоим переменной promise новый объект Promise, полученный вызовом метода then() предыдущего объекта Promise в цепочке. Это и есть шаблон асинхронных итераций с использованием объектов Promise.
В конце цикла переменная promise будет содержать объект Promise, который вернул последний вызов then() в цикле, поэтому он будет разрешен после разрешения всех объектов Promise в цепочке.
Выполнение промиса
При запуске мы выполняем промис и ожидаем успешного выполнения, используя обратный вызов .
Так как мы создали промис, а затем сразу же выполнили — передав Alex is cool, мы сразу увидем результат в консоле.
Если нужно сделать через некоторое время
Если мы хотим выполнить некоторую обработку в фоновом режиме или сделать AJAX-запрос, а затем, когда данные вернутся, сделать . По сути, всё сводится к тому, что «я не хочу останавливать выполнение JavaScript, я просто хочу начать запрос, а затем, когда он вернется, разберусь с этим результатом».
Давай посмотрим, что произойдет, когда мы установим здесь тайм-аут в 1 секунду.
Через секунду в консоле появится Alex is cool. Точно так же мы можем вызвать :
В консоле увидим . — это настоящая ошибка. Почему (не перехвачено промисом)? Потому что мы не словили и не обработали ошибку в промисе. Для этого нам нужно добавить в нашу цепочку , передать ошибку и вывести ее через .
Теперь в консоле браузера через 1 секунду будет только реальная ошибка, которую мы передали. В идеале, для более детальной информации, где произошла ошибка, мы должны передать объект , а не просто строку. Поэтому мы обернем строку в объект , а затем, когда ошибка выведется в консоль, мы увидим, что у нас есть информация о том, где она произошла.
Bigger example: fetch
In frontend programming promises are often used for network requests. So let’s see an extended example of that.
We’ll use the fetch method to load the information about the user from the remote server. It has a lot of optional parameters covered in separate chapters, but the basic syntax is quite simple:
This makes a network request to the and returns a promise. The promise resolves with a object when the remote server responds with headers, but before the full response is downloaded.
To read the full response, we should call the method : it returns a promise that resolves when the full text is downloaded from the remote server, with that text as a result.
The code below makes a request to and loads its text from the server:
The object returned from also includes the method that reads the remote data and parses it as JSON. In our case that’s even more convenient, so let’s switch to it.
We’ll also use arrow functions for brevity:
Now let’s do something with the loaded user.
For instance, we can make one more request to GitHub, load the user profile and show the avatar:
The code works; see comments about the details. However, there’s a potential problem in it, a typical error for those who begin to use promises.
Look at the line : how can we do something after the avatar has finished showing and gets removed? For instance, we’d like to show a form for editing that user or something else. As of now, there’s no way.
To make the chain extendable, we need to return a promise that resolves when the avatar finishes showing.
Like this:
That is, the handler in line now returns , that becomes settled only after the call of in . The next in the chain will wait for that.
As a good practice, an asynchronous action should always return a promise. That makes it possible to plan actions after it; even if we don’t plan to extend the chain now, we may need it later.
Finally, we can split the code into reusable functions:
Promises States In Javascript
The Promise is the proxy for the value not necessarily known when a promise is created. The promise allows you to associate the handlers with an asynchronous action’s eventual success or failure reason.
This lets asynchronous methods return the values like synchronous methods: instead of immediately returning a final value, an asynchronous method returns a promise to supply value at some point in the future.
The Promise is in one of these states:
- Pending: an initial state or pending state, neither fulfilled nor rejected.
- Fulfilled: meaning that the operation completed successfully.
- Rejected: meaning that the operation failed.
Сложный кейс с промисами, и главное преимущество промисов — колбеки
Попалась мне интереснейшая задача «Платежная система отвечает об успешной оплате не сразу, поэтому придется слать несколько запросов в течение 30 секунд, при этом держать пользователя в режиме прелоадера, при этом если оплата пройдет раньше чем 30 секунд, то из цикла нужно выйти и отключить прелоадер, и если за 30 секунд ответа не получено, то показать ошибку».
Архитектура:
- Интервал запросов к серверу 1 секунда.
- Необходим один большой (глобальный) промис, чтобы было удобно отключить прелоадер.
- До входа в асинхронный код нужно включить прелоадер.
- Внутри асинхронного кода должно произойти «нечто ужасное» без потери читабельности.
- Отключение прелоадера должно происходить в финальном коде, независимо от того, успешная оплата, или ошибка, и независимо от того, сколько промисов будет использоваться в асинхронном коде.
Для удобства сопоставления алгоритма и архитектуры код совсем чуть-чуть упрощен, и совпадает с оригинальным на 90%.
Результат:
На данном примере видно как используются колбэки — у нас есть полный простор в передаче ошибок в родительский промис, их множественный вызов в разных местах, когда нам необходимо. И также максимальный простор для выбора момента уведомления «родителя» об успешном окончании, тем самым мы решаем 5 пункт из запланированной архитектуры.
Пример использования
Базовое использования метода
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
Summary
There are 6 static methods of class:
- – 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.
-
(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).
- – waits for the first promise to settle, and its result/error becomes the outcome.
- (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 .
- – makes a resolved promise with the given value.
- – makes a rejected promise with the given error.
Of all these, is probably the most common in practice.
JavaScript Promise Methods
There are various methods available to the Promise object.
Method | Description |
---|---|
Waits for all promises to be resolved or any one to be rejected | |
Waits until all promises are either resolved or rejected | |
Returns the promise value as soon as any one of the promises is fulfilled | |
Wait until any of the promises is resolved or rejected | |
Returns a new Promise object that is rejected for the given reason | |
Returns a new Promise object that is resolved with the given value | |
Appends the rejection handler callback | |
Appends the resolved handler callback | |
Appends a handler to the promise |
To learn more about promises in detail, visit JavaScript Promises.
Promise.allSettled
Новая возможность
Эта возможность была добавлена в язык недавно.
В старых браузерах может понадобиться полифил.
завершается с ошибкой, если она возникает в любом из переданных промисов. Это подходит для ситуаций «всё или ничего», когда нам нужны все результаты для продолжения:
Метод всегда ждёт завершения всех промисов. В массиве результатов будет
- для успешных завершений,
- для ошибок.
Например, мы хотели бы загрузить информацию о множестве пользователей. Даже если в каком-то запросе ошибка, нас всё равно интересуют остальные.
Используем для этого :
Массив в строке будет таким:
То есть, для каждого промиса у нас есть его статус и значение/ошибка.
Если браузер не поддерживает , для него легко сделать полифил:
В этом коде берёт аргументы, превращает их в промисы (на всякий случай) и добавляет каждому обработчик .
Этот обработчик превращает успешный результат в , а ошибку в . Это как раз и есть формат результатов .
Затем мы можем использовать , чтобы получить результаты всех промисов, даже если при выполнении какого-то возникнет ошибка.
Javascript Promise
JavaScript is the single-threaded scripting language. Everything happens in a sequence, it is written, line-by-line. But, in the asynchronous operations occur in the order they complete.
Events are very good for things that can happen multiple times on the same object as keyup, touchstart, mouseover, etc.
With those events, you don’t care about what happened before you attached a listener. But when it comes to the async behavior of success/failure, Callback functions are not as helpful as promises.
The promise constructor takes in one argument: a callback function with two parameters called resolve and reject. We can use promise as the two different approaches.
- Creating a Promise
- Consuming a Promise
Перехват ошибок
Выше мы рассмотрели «идеальный случай» выполнения, когда ошибок нет.
А что, если github не отвечает? Или JSON.parse бросил синтаксическую ошибку при обработке данных?
Да мало ли, где ошибка…
Правило здесь очень простое.
При возникновении ошибки – она отправляется в ближайший обработчик .
Такой обработчик нужно поставить через второй аргумент или, что то же самое, через .
Чтобы поймать всевозможные ошибки, которые возникнут при загрузке и обработке данных, добавим в конец нашей цепочки:
В примере выше ошибка возникает в первом же , но с тем же успехом поймал бы ошибку во втором или в .
Принцип очень похож на обычный : мы делаем асинхронную цепочку из , а затем, в том месте кода, где нужно перехватить ошибки, вызываем .
А что после ?
Обработчик получает ошибку и должен обработать её.
Есть два варианта развития событий:
- Если ошибка не критичная, то возвращает значение через , и управление переходит в ближайший .
- Если продолжить выполнение с такой ошибкой нельзя, то он делает , и тогда ошибка переходит в следующий ближайший .
Это также похоже на обычный – в блоке ошибка либо обрабатывается, и тогда выполнение кода продолжается как обычно, либо он делает . Существенное отличие – в том, что промисы асинхронные, поэтому при отсутствии внешнего ошибка не «вываливается» в консоль и не «убивает» скрипт.
Ведь возможно, что новый обработчик будет добавлен в цепочку позже.
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 )
Creating a promise: the Promise constructor
To create a promise in JavaScript, you use the constructor:
The constructor accepts a function as an argument. This function is called the .
The executor accepts two functions with the names, by convention, and .
When you call the , the is called automatically.
Inside the executor, you manually call the function if the executor is completed successfully and invoke the function in case of an error occurs.
If you embed the above JavaScript code in an HTML document and check the console window, you will see that the promise is resolved because the variable is set to .
To see the pending state of the promise, we wrap the code of the executor in the function:
Now, you see that the promise starts with the state with the value is . The promise value will be returned later once the promise is completed.
After about 3 seconds, type the in the console window, you will see that the state of the promise becomes and the promise value is the string that we passed to the function.
So calling the function moves the promise object to the fulfilled state. If you change the value of the variable to and run the script again:
You will see an error message and the state of the promise becomes after 3 seconds:
In other words, calling the method moves the promise object to the state.
The following picture illustrates the states of a promise and the effect of calling the and functions:
Once the promise reaches either fulfilled state or rejected state, it stays in that state and can’t switch.
In other words, a promise cannot go from the fulfilled state to the rejected state and vice versa. It also cannot go back from the fulfilled state or rejected state to the pending state.
If the promise reaches fulfilled state or rejected state, the promise is resolved.
Once a new object is created, it is in the pending state until it is resolved. To schedule a callback when the promise is either resolved or rejected, you call the methods of the object: , , and .