Одним из примуществом промисов является то, что они позволяют создавать цепочки промисов. Так, ранее мы рассмотрели применение методов then() и
catch() для получения и обработки результатов и ошибок асинхронной операции. При выполнении эти методы генерируют новый объект
Promise, для которого мы также можем вызвать методы then()
и catch()
, и, таким образом, построить цепочку промисов.
Благодаря этому мы можем обрабатывать подряд несколько асинхронных операций - одна за другой.
promise.then(..).then(..).then(..)
Возвращаемое значение из функции-обработчика в методе then() передается в последующий вызов метода then()
в цепочке:
const helloPromise = new Promise(function(resolve){ resolve("Hello"); }) const worldPromise = helloPromise.then(function(value){ // возвращаем новое значение return value + " World"; }); const metanitPromise = worldPromise.then(function(value){ // возвращаем новое значение return value + " from METANIT.COM"; }); metanitPromise.then(function(finalValue){ // получаем финальное значение console.log(finalValue); // Hello World from METANIT.COM });
Здесь для большей ясности весь процесс раздел на раздельные промисы: helloPromise, worldPromise и metanitPromise.
Рассмотрим поэтапно.
Сначала создается промис helloPromise
:
const helloPromise = new Promise(function(resolve){ resolve("Hello"); });
В асинхронной операции с помощью вызова resolve("Hello")
промис переводится в состояние fulfilled
, то есть выполнение операции
успешно завершено. А во вне передается значение "Hello".
Далее у промиса helloPromise
вызывается метод then():
const worldPromise = helloPromise.then(function(value){ // возвращаем новое значение return value + " World"; });
В качестве значения параметра value
функция обработчика получает строку "Hello" и затем возвращает строку "Hello World". Эта строка затем можно будет получить через метод
then()
нового промиса, который генерируется вызовом helloPromise.then()
и который называется здесь worldPromise
.
Затем аналогичным образом у промиса worldPromise
вызывается метод then():
const metanitPromise = worldPromise.then(function(value){ // возвращаем новое значение return value + " from METANIT.COM"; });
В качестве значения параметра value
функция обработчика получает строку "Hello World" и затем возвращает строку "Hello World from METANIT.COM".
Вызов worldPromise.then()
возвращает новый промис metanitPromise
.
На последним этапе у промиса metanitPromise
вызывается метод then():
metanitPromise.then(function(finalValue){ console.log(finalValue); // Hello World from METANIT.COM });
Здесь через параметр finalValue
получаем финальное значение - строку "Hello World from METANIT.COM" и выводим на консоль. После этого цепочка завершена.
Для большей краткости и наглядности мы можем упростить цепочку:
new Promise(resolve => resolve("Hello")) .then(value => value + " World") .then(value => value + " from METANIT.COM") .then(finalValue => console.log(finalValue));
function generateNumber(str){ return new Promise((resolve, reject) => { const parsed = parseInt(str); if (isNaN(parsed)) reject("Not a number"); else resolve(parsed); }); }; function printNumber(str){ generateNumber(str) .then(value => console.log(value)) .catch(error => console.log(error)); } printNumber("rty"); // Not a number printNumber("3"); // 3
В данном случае функция generateNumber()
возвращает промис, в котором получаем извне некоторое значение,
пытаемся конвертировать его в число. В функции printNumber()
вызываем эту функцию и у полученного промиса создаем небольшую цепочку из
методов then()
и catch()
.
Если конвертация строки в число в промисе прошла успешно, то распарсенное число передачется в функцию resolve()
:
else resolve(parsed)
В этом случае при получении этого результата срабатывает метод then(), который в данном случае выводит полученное значение на консоль:
.then(value => console.log(value))
Метод catch() при отстутствии ошибок не выполняется.
Однако если передаеваемое значение невозможно конвертировать в число, соответственно в промисе выполняется вызов
if (isNaN(parsed)) reject("Not a number");
В этом случае метод then()
игнорируется, и выполнение переходит к вызову
.catch(error => console.log(error));
Теперь усложним цепочку. Пусть у нас в цепочке выполняется сразу несколько промисов:
function generateNumber(str){ return new Promise((resolve, reject) => { const parsed = parseInt(str); if (isNaN(parsed)) reject("Not a number"); else resolve(parsed); }); }; function printNumber(str){ generateNumber(str) .then(value => { if (value===4) throw "Несчастливое число"; return value * value; }) .then(finalValue => console.log(`Result: ${finalValue}`)) .catch(error => console.error(error)); } printNumber("rty"); // Not a number printNumber("3"); // Result: 9 printNumber("4"); // Несчастливое число printNumber("5"); // Result: 25
Здесь для простоты весь код вынесен в функцию generateNumber()
, которая создает цепочку промисов. В этой цепочке промисов
также получаем извне некоторое значение, пытаемся конвертировать его в число, и затем вычисляем его квадрат и выводит на консоль. В конце которой находится метод catch()
. В этот метод передается обработчик ощибки,
который получает ошибку и выводит ее на консоль. В итоге если в цепочке промисов на одном из этапов генерируется ошибка (в силу внутренней работы кода, например, при генерации ошибки с помощью оператора throw
, либо при вызове функции reject()
),
то все последующие вызовы метода then()
, которые содержат только обработку значения, игнорируются, и выполнение цепочки переходит к методу
catch()
.
Для примера вызываем функцию printNumber()
, передавая в нее различные исходные данные. Например, при выполнении вызова
printNumber("rty"); // Not a number
При этом стоит отметить, что, поскольку catch()
возвращает объект Promise, то далее также можно продолжить цепочку:
function generateNumber(str){ return new Promise((resolve, reject) => { const parsed = parseInt(str); if (isNaN(parsed)) reject("Not a number"); else resolve(parsed); }); }; function printNumber(str){ generateNumber(str) .then(value => value * value) .then(value => console.log(`Result: ${value}`)) .catch(error => console.error(error)) .then(() => console.log("Work has been done")); } printNumber("3"); // Result: 9 // Work has been done
Причем метод then()
после catch()
будет вызываться, даже если не произошло ошибок и сам метод catch()
не выполнялся.
И мы даже можем из функции-обработчика ошибки в catch()
также можем передавать некоторое значение и получать через последующий метод then()
:
function generateNumber(str){ return new Promise((resolve, reject) => { const parsed = parseInt(str); if (isNaN(parsed)) reject("Not a number"); else resolve(parsed); }); }; function printNumber(str){ generateNumber(str) .then(value => value * value) .then(value => console.log(`Result: ${value}`)) .catch(error => { console.log(error); return 0; }) .then(value => console.log("Status code:", value)); } printNumber("ert3"); // Not a number // Status code: 0
Кроме методов then()
и catch()
объект Promise для обработка результата также предоставляет
метод finally(). Этот метод выполняется в конце цепочки промисов вне зависимости произошла ошибка или выполнение промиса прошло успешно.
В качестве параметра метод принимает функцию, которая выполняет некоторые финальные действия по обработке работы промиса:
function generateNumber(str){ return new Promise((resolve, reject) => { const parsed = parseInt(str); if (isNaN(parsed)) reject("Not a number"); else resolve(parsed); }); }; function printNumber(str){ generateNumber(str) .then(value => console.log(value)) .catch(error => console.log(error)) .finally(() => console.log("End")); } printNumber("3"); printNumber("triuy");
Здесь мы два раза обращаемся к промису, возвращаемому функцией generateNumber. В одном случае строка удачно сконвертируется в число, в другом же - произойдет ошибка. Однако вне
зависимости от отсутствия или наличия ошибки в обоих случаях будет выполняться метод finally()
, который выведет на консоль строку "End".
Метод finally()
возвращает объект Promise, поэтому после него можно продолжить продолжить цепочку:
function generateNumber(str){ return new Promise((resolve, reject) => { const parsed = parseInt(str); if (isNaN(parsed)) reject("Not a number"); else resolve(parsed); }); }; function printNumber(str){ generateNumber(str) .then(value => console.log(value)) .catch(error => console.log(error)) .finally(() => console.log("Выполнение промиса завершено")) .then(() => console.log("Промис все еще работает")); } printNumber("3");
Консольный вывод:
3 Выполнение промиса завершено Промис все еще работает
Стоит отметить что в метод then()
также можно передать данные. Но эти данные передаются не из метода finally()
, а из предыдущего метода
then()
или catch()
:
function generateNumber(str){ return new Promise((resolve, reject) => { const parsed = parseInt(str); if (isNaN(parsed)) reject("Not a number"); else resolve(parsed); }); }; function printNumber(str){ generateNumber(str) .then(value => { console.log(value); return "hello from then"; }) .catch(error => { console.log(error); return "hello from catch"; }) .finally(() => { console.log("End"); return "hello from finally"; }) .then(message => console.log(message)); } printNumber("3");
3 End hello from then