Одним из примуществом промисов является то, что они позволяют создавать цепочки промисов. Так, ранее мы рассмотрели применение методов 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