Если вы занимаетесь веб-разработкой на JavaScript, я думаю, вы уже знакомы с Promise
и неоднократно сталкивались с адом обратных вызовов. Цепочка промисов в JavaScript — это один из способов решить проблему ада обратных вызовов, и мы обсудим его в этой статье. Тем не менее, давайте немного подытожим для тех, кто не знаком с понятиями.
Что такое JavaScript Promise
?
В JavaScript (ES6 и выше) Promise
— это объект, представляющий состояние и результат асинхронных операций, таких как вызов API или чтение/запись ввода-вывода. Состояния включают ожидание, выполнено и отклонено.
- Ожидание: операция выполняется, и результат не возвращен.
- Выполнено: операция прошла успешно, результат возвращен.
- Отклонено: операция завершилась неудачно, и была возвращена ошибка.
Promise
имеет два метода: then
и catch
. Метод then
принимает обратный вызов действия, которое должно быть инициировано после выполнения обещания, в то время как catch
запускается всякий раз, когда обещание отклоняется.
// Asynchronous API call operation
getWeatherTodayPromise
.then((weatherForecast) => { // Fulfilled
// Synchronous operation
display(weatherForecast)
})
.catch((error) = > { // Rejected
console.error(error)
})
Что такое ад обратного вызова?
Короче говоря, это когда ваши обратные вызовы были вложены на несколько уровней до такой степени, что они становятся неуправляемыми. Это может произойти в любом языке программирования и чаще встречается в асинхронных операциях. Глубоко вложенные обещания и обратные вызовы в JavaScript — это всего лишь одна из разновидностей ада обратных вызовов, и примеры в статье основаны на дисперсии этого ада обратных вызовов.
// A typical callback hell which involves multiple JavaScript promises
firstPromise
.then(secondPromise
.then(thirdPromise
.then(...).catch(...)
).catch(...)
).catch(...)
Читабельность — это одно, но ад обратных вызовов может вызвать другие проблемы с областью видимости. Типичным является сокрытие (проглатывание) ошибок, когда ошибка, вызванная внутренним обещанием, не была обнаружена.
// Asynchronous API call operation getWeatherTodayPromise .then((weatherForecast) => { // Asynchronous IO operation writeWeatherForecastToLogFilePromise(weatherForecast) // FAILED
...
// Unlike try-catch, there is no outer "catch-all" solution // You must have a "catch" at every nested promise // Or else, the promise is not terminated, and the error information is lost .catch((error) => { // IO error is caught here console.error("Inner promise", error) }) }) .catch((error) = > { // IO error is NOT caught here console.error("Outer promise", error) })
Что такое цепочка промисов в JavaScript?
Вы только что узнали, что ад обратных вызовов указывает на неуправляемые вложенные уровни обратных вызовов. С учетом сказанного, один из способов решить проблему - сделать обратные вызовы НЕ вложенными. Сделайте его мелким!
Цепочка обещаний в JavaScript — это когда несколько методов then
и catch
вызываются последовательно, чтобы полностью удалить вложенные уровни, сохраняя при этом предполагаемые результаты. Можно сказать, что это один из способов рефакторинга вашей кодовой базы.
// Promise chaining firstPromise .then(() => secondPromise) .then(() => thirdPromise) .then(...) .catch(...) .catch(...) .catch(...) .then(...)
// A typical callback hell which involves multiple JavaScript promises firstPromise .then(secondPromise .then(thirdPromise .then(...).catch(...) ).catch(...) ).catch(...)
Если вы имеете дело с обычными функциями на основе обратного вызова (НЕ промисами), вам нужно сначала промисифицировать функции, чтобы применить цепочку промисов. Есть способы сделать это, например, с помощью библиотеки es6-promisify
.
Как работает цепочка промисов в JavaScript?
then
иcatch
— это методы объектаPromise
, поэтому для создания цепочки обратный вызов в методеthen
должен возвращать новыйPromise
.
// Correct implementation // "() => something" is a shorthand for "() => {return something}) const secondPromise = firstPromise.then(() => newPromise) secondPromise.then(() => anotherPromise).then(...)
// Wrong implementation and an exception is raised firstPromise.then(() => null).then(...)
- Результат обещания переносится на следующий
then
.
getWeatherTodayPromise
.then(weatherForecastResult => writeWeatherForecastToLogFilePromise(weatherForecastResult))
// writeWeatherForecastToLogFilePromise(weatherForecastResult) if fulfilled will provide "ioWriteResult"
.then(ioWriteResult => Promise.all([
anotherPromise(ioWriteResult),
andSomethingElsePromise(),
]))
.then(listOfResults => ...)
- Мы можем вручную инициировать и вернуть новый объект
Promise
, чтобы сформировать цепочку промисов. Вместо того, чтобы выполнять задачи в одном обратном вызове, вы можете применить эту технику для сегментации кодовой базы на более мелкие фрагменты.
firstPromise
.then(() => {
const isSuccess = synchronousOperation() // boolean
return isSuccess ? Promise.resolve("Success") : Promise.reject(new Error("404"))
})
.then((result) => console.log(result)) // Print "Success"
.catch(error) => console.error(error)) // Print an error with "404" message
- Метод
then
имеет второй и необязательный аргументonRejectedCallback
, но поскольку мы его не используем, всякий раз, когда возникает исключение, браузер просматривает всю цепочку промисов, чтобы найти первый приемлемыйcatch
для данной ошибки. - Используя цепочку промисов, вы можете иметь одно внешнее «универсальное» решение, такое как
try-catch
, поэтому больше не будет возможности проглатывания ошибок. У вас может быть условный оператор в одномcatch
для нескольких ошибок, или вы можете разделить их на несколько разделенныхcatch
, как показано ниже.
rejected5xxPromise
.catch(HTTP 4xx) // Browser: "Not here"
.catch(HTTP 5xx) // Browser: "Okay, this catches the 5xx error"
.catch(Other unexpected errors) // Skip
- Вы можете связать
then
послеcatch
. Это означает «всегда действовать, несмотря ни на что».
rejected5xxPromise
.catch(HTTP 5xx)
.then(console.log("This line is always printed out"))
Заворачивать
Цепочка обещаний JavaScript — это простая, но мощная функция для решения распространенной проблемы с вложенными обратными вызовами (ад обратных вызовов). Чтобы связать обещания, нужно помнить два основных момента.
- Можно последовательно вызвать несколько
then
иcatch
, напримерpromise.then(...).then(...).catch(...).catch(...)
. - Обратный вызов в методе
then
должен возвращать новый объектPromise
, чтобы цепочка могла быть продолжена.
Это правило также относится к TypeScript. В ES2016 (он же ES7) была введена функция async/await
, которая делает нашу жизнь еще проще. Тем не менее, если вы по какой-то причине не можете использовать функцию ES7, то цепочка промисов — отличный выбор для рефакторинга вашей кодовой базы.
Заинтересованы в веб-разработке? Другие мои статьи могут быть вам полезны!