Что такое promise js
Перейти к содержимому

Что такое promise js

  • автор:

Promise

Объект Promise используется для отложенных и асинхронных вычислений.

Интерактивный пример

Синтаксис

new Promise(executor); new Promise(function(resolve, reject)  . >); 

Параметры

Объект функции с двумя аргументами resolve и reject . Функция executor получает оба аргумента и выполняется сразу, ещё до того как конструктор вернёт созданный объект. Первый аргумент ( resolve ) вызывает успешное исполнение промиса, второй ( reject ) отклоняет его. Обычно функция executor описывает выполнение какой-то асинхронной работы, по завершении которой необходимо вызвать функцию resolve или reject . Обратите внимание, что возвращаемое значение функции executor игнорируется.

Описание

Интерфейс Promise (промис) представляет собой обёртку для значения, неизвестного на момент создания промиса. Он позволяет обрабатывать результаты асинхронных операций так, как если бы они были синхронными: вместо конечного результата асинхронного метода возвращается своего рода обещание (дословный перевод слова «промис») получить результат в некоторый момент в будущем.

Promise может находиться в трёх состояниях:

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

При создании промис находится в ожидании (pending), а затем может стать исполненным (fulfilled), вернув полученный результат (значение), или отклонённым (rejected), вернув причину отказа. В любом из этих случаев вызывается обработчик, прикреплённый к промису методом then . (Если в момент назначения обработчика промис уже исполнен или отклонён, обработчик всё равно будет вызван, т.е. асинхронное исполнение промиса и назначение обработчика не будет происходить в «состоянии гонки», как, например, в случае с событиями в DOM.)

Так как методы Promise.prototype.then() и Promise.prototype.catch() сами возвращают промис, их можно вызывать цепочкой, создавая соединения.

Примечание: говорят, что промис находится в состоянии завершён (settled) когда он или исполнен или отклонён, т.е. в любом состоянии, кроме ожидания (это лишь форма речи, не являющаяся настоящим состоянием промиса). Также можно встретить термин исполнен (resolved) — это значит что промис завершён или «заблокирован» в ожидании завершения другого промиса. В статье состояния и fates приводится более подробное описание терминологии.

Свойства

Значение свойства всегда равно 1 (количество аргументов конструктора).

Представляет прототип для конструктора Promise .

Методы

Ожидает исполнения всех промисов или отклонения любого из них.

Возвращает промис, который исполнится после исполнения всех промисов в iterable . В случае, если любой из промисов будет отклонён, Promise.all будет также отклонён.

Ожидает завершения всех полученных промисов (как исполнения так и отклонения).

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

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

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

Возвращает промис, отклонённый из-за reason .

Возвращает промис, исполненный с результатом value .

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

Объект Promise создаётся при помощи ключевого слова new и своего конструктора. Конструктор Promise принимает в качестве аргумента функцию, называемую «исполнитель» (executor function). Эта функция должна принимать две функции-колбэка в качестве параметров. Первый из них ( resolve ) вызывается, когда асинхронная операция завершилась успешно и вернула результат своего исполнения в виде значения. Второй колбэк ( reject ) вызывается, когда операция не удалась, и возвращает значение, указывающее на причину неудачи, чаще всего объект ошибки.

const myFirstPromise = new Promise((resolve, reject) =>  // выполняется асинхронная операция, которая в итоге вызовет: // // resolve(someValue); // успешное завершение // или // reject("failure reason"); // неудача >); 

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

function myAsyncFunction(url)  return new Promise((resolve, reject) =>  const xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = () => resolve(xhr.responseText); xhr.onerror = () => reject(xhr.statusText); xhr.send(); >); > 

Примеры

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

js
let myFirstPromise = new Promise((resolve, reject) =>  // Мы вызываем resolve(. ), когда асинхронная операция завершилась успешно, и reject(. ), когда она не удалась. // В этом примере мы используем setTimeout(. ), чтобы симулировать асинхронный код. // В реальности вы, скорее всего, будете использовать XHR, HTML5 API или что-то подобное. setTimeout(function ()  resolve("Success!"); // Ура! Всё прошло хорошо! >, 250); >); myFirstPromise.then((successMessage) =>  // successMessage - это что угодно, что мы передали в функцию resolve(. ) выше. // Это необязательно строка, но если это всего лишь сообщение об успешном завершении, это наверняка будет она. console.log("Ура! " + successMessage); >); 

Продвинутый пример

html
button id="btn">Создать Promise!button> div id="log">div> 

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

var promiseCount = 0; function testPromise()  var thisPromiseCount = ++promiseCount; var log = document.getElementById('log'); log.insertAdjacentHTML('beforeend', thisPromiseCount + ') Запуск (запуск синхронного кода) '); // Создаём промис, возвращающее 'result' (по истечении 3-х секунд) var p1 = new Promise( // Функция разрешения позволяет завершить успешно или // отклонить промис function(resolve, reject)  log.insertAdjacentHTML('beforeend', thisPromiseCount + ') Запуск промиса (запуск асинхронного кода) '); // Это всего лишь пример асинхронности window.setTimeout( function()  // Промис исполнен! resolve(thisPromiseCount) >, Math.random() * 2000 + 1000); >); // Указываем, что сделать с исполненным промисом p1.then( // Записываем в протокол function(val)  log.insertAdjacentHTML('beforeend', val + ') Промис исполнен (асинхронный код завершён) '); >); log.insertAdjacentHTML('beforeend', thisPromiseCount + ') Промис создан (синхронный код завершён) '); > 
if ("Promise" in window)  btn = document.getElementById("btn"); btn.addEventListener("click", testPromise); > else  log = document.getElementById("log"); log.innerHTML = "Live example not available as your browser doesn't support the Promise interface."; > 
if ("Promise" in window)  let btn = document.getElementById("btn"); btn.addEventListener("click", testPromise); > else  log = document.getElementById("log"); log.innerHTML = "Демонстрация невозможна, поскольку ваш браузер не поддерживает интерфейс Promise."; > 

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

Загрузка изображения при помощи XHR

Другой простой пример использования Promise и XMLHttpRequest для загрузки изображения доступен в репозитории MDNpromise-test на GitHub. Вы также можете посмотреть его в действии. Каждый шаг прокомментирован и вы можете подробно исследовать Promise и XHR.

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

Specification
ECMAScript Language Specification
# sec-promise-objects

Совместимость с браузерами

BCD tables only load in the browser

Смотрите также

  • Спецификация Promises/A+
  • Jake Archibald: JavaScript Promises: There and Back Again
  • Domenic Denicola: Callbacks, Promises, and Coroutines – Asynchronous Programming Pattern in JavaScript
  • Matt Greer: JavaScript Promises . In Wicked Detail
  • Forbes Lindesay: promisejs.org
  • Nolan Lawson: We have a problem with promises — Common mistakes with promises
  • Promise polyfill
  • Udacity: JavaScript Promises

Found a content problem with this page?

  • Edit the page on GitHub.
  • Report the content issue.
  • View the source on GitHub.

This page was last modified on 7 авг. 2023 г. by MDN contributors.

Your blueprint for a better internet.

Что такое promise js

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

Промис может находиться в одном из следующих состояний:

  • pending (состояние ожидания): начальное состояние, промис создан, но выполнение еще не завершено
  • fulfilled (успешно завершено): действие, которое представляет промис, успешно завершено
  • rejected (завершено с ошибкой): при выполнении действия, которое представляет промис, произошла ошибка

Для создания промиса применяется конструктор типа Promise :

new Promise(executor)

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

const myPromise = new Promise(function()< console.log("Выполнение асинхронной операции"); >);

Здесь функция просто выводит на консоль сообщение. Соответственно при выполнении этого кода мы увидим на консоли сообщение "Выполнение асинхронной операции" .

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

Для эмуляции асинхронности определим несколько промисов:

const myPromise3000 = new Promise(function()< console.log("[myPromise3000] Выполнение асинхронной операции"); setTimeout(()=>console.log("[myPromise3000] Завершение асинхронной операции"), 3000); >); const myPromise1000 = new Promise(function()< console.log("[myPromise1000] Выполнение асинхронной операции"); setTimeout(()=>console.log("[myPromise1000] Завершение асинхронной операции"), 1000); >); const myPromise2000 = new Promise(function()< console.log("[myPromise2000] Выполнение асинхронной операции"); setTimeout(()=>console.log("[myPromise2000] Завершение асинхронной операции"), 2000); >);

Здесь определены три однотипных промиса. Чтобы каждый из них не выполнялся сразу, они используют функцию setTimeout с задержкой в несколько секунд. Для разных промисов длительность задержки различается. И в данном случае мы получим следующий консольный вывод:

[myPromise3000] Выполнение асинхронной операции [myPromise1000] Выполнение асинхронной операции [myPromise2000] Выполнение асинхронной операции [myPromise1000] Завершение асинхронной операции [myPromise2000] Завершение асинхронной операции [myPromise3000] Завершение асинхронной операции

Здесь мы видим, что первым начал выполняться промис myPromise3000, однако он же завершился последним, так как для него установлено наибольшее время задержки - 3 секунды. Однако его задержка не помешала выполнению остальных промисов.

resolve и reject

Как правило, функция, которая передается в конструктор Promise, принимает два параметра:

const myPromise = new Promise(function(resolve, reject)< console.log("Выполнение асинхронной операции"); >);

Оба этих параметра - resolve и reject также представляют функции. И каждая из этих функций принимает параметр любого типа.

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

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

Успешное выполнение промиса

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

const myPromise = new Promise(function(resolve)< console.log("Выполнение асинхронной операции"); resolve("Привет мир!"); >);

Функция resolve() вызывается в конце выполняемой операции после всех действий. При вызове этой функции промис переходит в состояние fulfilled (успешно выполнено).

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

const myPromise = new Promise(function(resolve, reject)< console.log("Выполнение асинхронной операции"); return "Привет мир!"; >);

Данное возвращаемое значение мы не сможем передать во вне. И если действительно надо возвратить какой-то результат, то он передается в функцию resolve() .

Передача информации об ошибке

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

const myPromise = new Promise(function(resolve, reject)< console.log("Выполнение асинхронной операции"); reject("Переданы некорректные данные"); >);

При вызове функции reject() промис переходит в состояние rejected (завершилось с ошибкой).

Объединение resolve и reject

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

const x = 4; const y = 0; const myPromise = new Promise(function(resolve, reject) < if(y === 0) < reject("Переданы некорректные данные"); >else < const z = x / y; resolve(z); >>);

В данном случае, если значени константы y равно 0, то сообщаем об ошибке, вызывая функцию reject() . Если не равно 0, то выполняем операцию деления и передаем результат в функцию resolve() .

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

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

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

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

function doSomethingOldStyle(successCallback, failureCallback)  console.log("Готово."); // Успех в половине случаев. if (Math.random() > 0.5)  successCallback("Успех"); > else  failureCallback("Ошибка"); > > function successCallback(result)  console.log("Успешно завершено с результатом " + result); > function failureCallback(error)  console.log("Завершено с ошибкой " + error); > doSomethingOldStyle(successCallback, failureCallback); 

…современные функции возвращают промис, в который вы записываете ваши колбэки:

function doSomething()  return new Promise((resolve, reject) =>  console.log("Готово."); // Успех в половине случаев. if (Math.random() > 0.5)  resolve("Успех"); > else  reject("Ошибка"); > >); > const promise = doSomething(); promise.then(successCallback, failureCallback); 
doSomething().then(successCallback, failureCallback); 

Мы называем это асинхронным вызовом функции. У этого соглашения есть несколько преимуществ. Давайте рассмотрим их.

Гарантии

В отличие от старомодных переданных колбэков промис даёт некоторые гарантии:

  • Колбэки никогда не будут вызваны до завершения обработки текущего события в событийном цикле JavaScript.
  • Колбэки, добавленные через .then даже после успешного или неудачного завершения асинхронной операции, будут также вызваны.
  • Несколько колбэков может быть добавлено вызовом .then нужное количество раз, и они будут выполняться независимо в порядке добавления.

Но наиболее непосредственная польза от промисов - цепочка вызовов (chaining).

Цепочка вызовов

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

Вот в чём магия: функция then возвращает новый промис, отличающийся от первоначального:

let promise = doSomething(); let promise2 = promise.then(successCallback, failureCallback); 
let promise2 = doSomething().then(successCallback, failureCallback); 

Второй промис представляет завершение не только doSomething() , но и функций successCallback или failureCallback , переданных вами, а они тоже могут быть асинхронными функциями, возвращающими промис. В этом случае все колбэки, добавленные к promise2 будут поставлены в очередь за промисом, возвращаемым successCallback или failureCallback .

По сути, каждый вызванный промис означает успешное завершение предыдущих шагов в цепочке.

Раньше выполнение нескольких асинхронных операций друг за другом приводило к классической "Вавилонской башне" колбэков:

doSomething(function (result)  doSomethingElse( result, function (newResult)  doThirdThing( newResult, function (finalResult)  console.log("Итоговый результат: " + finalResult); >, failureCallback, ); >, failureCallback, ); >, failureCallback); 

В современных функциях мы записываем колбэки в возвращаемые промисы - формируем цепочку промисов:

doSomething() .then(function (result)  return doSomethingElse(result); >) .then(function (newResult)  return doThirdThing(newResult); >) .then(function (finalResult)  console.log("Итоговый результат: " + finalResult); >) .catch(failureCallback); 

Аргументы then необязательны, а catch(failureCallback) - это сокращение для then(null, failureCallback) . Вот как это выражено с помощью стрелочных функций:

doSomething() .then((result) => doSomethingElse(result)) .then((newResult) => doThirdThing(newResult)) .then((finalResult) =>  console.log(`Итоговый результат: $finalResult>`); >) .catch(failureCallback); 

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

Цепочка вызовов после catch

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

new Promise((resolve, reject) => < console.log('Начало'); resolve(); >) .then(() => < throw new Error('Где-то произошла ошибка'); console.log('Выведи это'); >) .catch(() => < console.log('Выведи то'); >) .then(() => < console.log('Выведи это, несмотря ни на что'); >);

В результате выведется данный текст:

Начало Выведи то Выведи это, несмотря ни на что

Заметьте, что текст "Выведи это" не вывелся, потому что "Где-то произошла ошибка" привела к отказу

Распространение ошибки

Вы могли ранее заметить, что failureCallback повторяется три раза в "pyramid of doom", а в цепочке промисов всего лишь один раз:

doSomething() .then(result => doSomethingElse(result)) .then(newResult => doThirdThing(newResult)) .then(finalResult => console.log(`Итоговый результат: $`)) .catch(failureCallback);

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

try < let result = syncDoSomething(); let newResult = syncDoSomethingElse(result); let finalResult = syncDoThirdThing(newResult); console.log(`Итоговый результат: $`); > catch(error)

Эта симметрия с синхронным кодом лучше всего показывает себя в синтаксическом сахаре async / await в ECMAScript 2017:

async function foo() < try < let result = await doSomething(); let newResult = await doSomethingElse(result); let finalResult = await doThirdThing(newResult); console.log(`Итоговый результат: $`); > catch(error) < failureCallback(error); >>

Работа данного кода основана на промисах. Для примера здесь используется функция doSomething() , которая встречалась ранее. Вы можете прочитать больше о синтаксисе здесь

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

Создание промиса вокруг старого колбэка

Promise может быть создан с помощью конструктора. Это может понадобится только для старых API.

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

setTimeout(() => saySomething("10 seconds passed"), 10000);

Смешивание старого колбэк-стиля и промисов проблематично. В случае неудачного завершения saySomething или программной ошибки, нельзя обработать ошибку.

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

const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); wait(10000).then(() => saySomething("10 seconds")).catch(failureCallback);

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

Композиция

Promise.resolve() и Promise.reject() короткий способ создать уже успешные или отклонённые промисы соответственно. Это иногда бывает полезно.

Promise.all() и Promise.race() - два метода запустить асинхронные операции параллельно.

Последовательное выполнение композиции возможно при помощи хитрости JavaScript:

[func1, func2].reduce((p, f) => p.then(f), Promise.resolve());

Фактически, мы превращаем массив асинхронных функций в цепочку промисов равносильно: Promise.resolve().then(func1).then(func2);

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

const applyAsync = (acc,val) => acc.then(val); const composeAsync = (. funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));

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

const transformData = composeAsync(func1, asyncFunc1, asyncFunc2, func2); transformData(data);

В ECMAScript 2017, последовательные композиции могут быть выполнены более простым способом с помощью async/await:

for (const f of [func1, func2])

Порядок выполнения

Чтобы избежать сюрпризов, функции, переданные в then никогда не будут вызваны синхронно, даже с уже разрешённым промисом:

Promise.resolve().then(() => console.log(2)); console.log(1); // 1, 2

Вместо немедленного выполнения, переданная функция встанет в очередь микрозадач, а значит выполнится, когда очередь будет пустой в конце текущего вызова JavaScript цикла событий (event loop), т.е. очень скоро:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); wait().then(() => console.log(4)); Promise.resolve().then(() => console.log(2)).then(() => console.log(3)); console.log(1); // 1, 2, 3, 4

Вложенность

Простые цепочки promise лучше оставлять без вложений, так как вложенность может быть результатом небрежной структуры. Смотрите распространённые ошибки.

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

doSomethingCritical() .then(result => doSomethingOptional() .then(optionalResult => doSomethingExtraNice(optionalResult)) .catch(e => <>)) // Игнорируется если необязательные параметр не выкинул исключение .then(() => moreCriticalStuff()) .catch(e => console.log("Критическая ошибка: " + e.message));

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

Внутренний оператор catch нейтрализует и перехватывает ошибки только от doSomethingOptional() и doSomethingExtraNice(), после чего код возобновляется с помощью moreCriticalStuff(). Важно, что в случае сбоя doSomethingCritical() его ошибка перехватывается только последним (внешним) catch.

Частые ошибки

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

// Плохой пример! Три ошибки! doSomething().then(function(result) < doSomethingElse(result) // Забыл вернуть промис из внутренней цепочки + неуместное влаживание .then(newResult =>doThirdThing(newResult)); >).then(() => doFourthThing()); // Забыл закончить цепочку методом catch

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

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

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

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

doSomething() .then(function(result) < return doSomethingElse(result); >) .then(newResult => doThirdThing(newResult)) .then(() => doFourthThing()) .catch(error => console.log(error));

Обратите внимание что () => x это сокращённая форма () => < return x; >.

Теперь у нас имеется единственная определённая цепочка с правильной обработкой ошибок.

Использование async / await предотвращает большинство, если не все вышеуказанные ошибки, но взамен появляется другая частая ошибка — забыть ключевое слово await .

Смотрите также

  • Promise.then()
  • Спецификация Promises/A+ (EN)
  • Нолан Лоусон (Nolan Lawson): У нас проблемы с промисами - распространённые ошибки (EN)

Found a content problem with this page?

  • Edit the page on GitHub.
  • Report the content issue.
  • View the source on GitHub.

Promises — JS: Синхронная асинхронность

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

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

Знакомству с промисами способствует понимание темы "конечные автоматы".

Начнем по традиции с примера:

const file = '/tmp/hello1.txt'; import  writeFile, readFile > from 'fs-promise'; writeFile(file, 'hello world') .then(() => readFile(file, 'utf8')) .then(contents => console.log(contents)) .catch(err => console.log(err)); // hello world 

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

Абзац выше – это пример того, как выглядит типичная программа, построенная на промисах. Так что такое промис?

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

  • Promise.prototype.then(onFulfilled, onRejected)
  • Promise.prototype.catch(onRejected)

Отсутствие callback hell происходит благодаря тому, что мы всегда работаем на уровне последовательных вызовов then , а не уходим в глубину.

Разберем пример выше по косточкам. Первый вызов writeFile(file, 'hello world') возвращает тот самый промис, и пока не важно, как он строится внутри, сейчас мы пытаемся понять то, как с ним работать.

// Вызов ничем не отличается кроме того, что мы не передаем колбек writeFile(file, 'hello world') 

После этого у нас есть два варианта:

  • Мы вызываем then и передаем функцию onFulfilled , которая будет вызвана в случае успешного выполнения асинхронной операции
  • Мы вызываем catch и передаем функцию onRejected , которая будет вызвана, в случае ошибок в результате выполнения асинхронной операции.

Функция onFulfilled принимает на вход данные, которые были получены в результате предыдущего выполнения. Таким образом идет передача данных по цепочке.

.then(() => readFile(file, 'utf8')) .then(contents => console.log(contents)) 

Данные, возвращаемые из функции onFulfilled , переходят по цепочке в функцию onFulfilled следующего then . Но если вернуть promise , то в следующем then окажутся данные, полученные в результате выполнения этого промиса, а не сам промис. Что и происходит в примере выше: мы возвращаем readFile() , а ниже получаем contents . То есть, промисы хорошо комбинируются друг с другом.

Конечный автомат

Теперь попробуем посмотреть внутрь промиса. С концептуальной точки зрения промис – это конечный автомат, у которого три состояния: pending , fulfilled , rejected .

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

Реализация

const promiseReadFile = filename =>  return new Promise((resolve, reject) =>  fs.readFile(filename, (err, data) =>  err ? reject(err) : resolve(data); >); >); >; 

Любая функция возвращающая промис, внутри себя создает объект промиса привычным способом. Конструктор Promise принимает на вход функцию, внутри которой запускается выполнение асинхронной операции. Делается это, кстати, сразу, промисы не являются примером отложенного (lazy) выполнения кода. Но это еще не все. Промис требует от нас некоторых действий для своей работы. Во входную функцию передаются две другие: reject и resolve . reject должна быть вызвана в случае ошибки с передачей внутрь объекта error , а resolve — в случае успешного завершения асинхронной операции с передачей внутрь данных, если они есть.

Ошибки

Ошибка обрабатывается ближайшим обработчиком onRejected в цепочке вызовов. При этом существует два варианта определения обработчика. Первый - через catch , второй - с помощью передачи в then второго параметра. Это продемонстрировано в примере ниже:

promiseReadFile('file1') .then(data => promiseWriteFile('file2', data)) .then(() => promiseReadFile('file3')) .then(data => console.log(data)) .catch(err => console.log(err)); // .then(null, err => console.log(err)); 

Promise.all

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

const readJsonFiles = filenames =>  // N.B. passing readJSON as a function, // not calling it with `()` return Promise.all(filenames.map(readJSON)); > readJsonFiles(['a.json', 'b.json']) .then(results =>  // results is an array of the values 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

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

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