
Не используйте вложенные обратные вызовы, вместо этого используйте цепочку промисов
26 мая 2022 г.Если вы занимаетесь веб-разработкой на JavaScript, я думаю, вы уже знакомы с Promise
и неоднократно сталкивались с адом обратных вызовов. Цепочка промисов в JavaScript — это один из способов решить проблему ада обратных вызовов, и мы обсудим его в этой статье. Тем не менее, давайте немного подытожим для тех, кто не знаком с понятиями.
Что такое обещание JavaScript?
В JavaScript (ES6 и выше) Promise — это объект, представляющий состояние и результат асинхронных операций, таких как вызов API или чтение/запись ввода-вывода. Состояния включают ожидание, выполнено и отклонено.
- Ожидание: операция выполняется, и результат не возвращен.
- Выполнено: операция прошла успешно, результат возвращен.
- Отклонено: операция завершилась неудачно, и была возвращена ошибка.
У Promise
есть два метода: then
и catch
. Метод then
принимает обратный вызов действия, которое будет инициировано после выполнения обещания, в то время как catch
запускается всякий раз, когда обещание отклонено.
```javascript
// Операция асинхронного вызова API
getWeatherTodayPromise
.then((weatherForecast) => { // Выполнено
// Синхронная работа
дисплей (прогноз погоды)
.catch((error) = > { // Отклонено
console.error(ошибка)
Что такое ад обратного вызова?
Короче говоря, это когда ваши обратные вызовы были вложены на несколько уровней до такой степени, что они становятся неуправляемыми. Это может произойти в любом языке программирования и чаще встречается в асинхронных операциях. Глубоко вложенные промис и обратный вызов в JavaScript — это всего лишь один из вариантов ада обратных вызовов, и примеры в статье основаны на дисперсии этого ада обратных вызовов.
```javascript
// Типичный ад обратного вызова, который включает несколько промисов JavaScript
первое обещание
.затем (второе обещание
.затем(третье обещание
.тогда(...).поймать(...)
).ловить(...)
).ловить(...)
Читабельность — это одно, но ад обратных вызовов может вызвать другие проблемы с областью видимости. Типичным является сокрытие (проглатывание) ошибок, когда ошибка, вызванная внутренним обещанием, не была обнаружена.
```javascript
// Операция асинхронного вызова API
getWeatherTodayPromise
.тог((прогноз погоды) => {
// Асинхронная операция ввода-вывода
writeWeatherForecastToLogFilePromise(weatherForecast) // FAILED
// В отличие от try-catch, здесь нет внешнего универсального решения
// У вас должен быть "улов" на каждом вложенном промисе
// В противном случае промис не завершается, и информация об ошибке теряется
.catch((ошибка) => {
// Здесь перехватывается ошибка ввода-вывода
console.error("Внутреннее обещание", ошибка)
.catch((ошибка) = > {
// ошибка ввода-вывода здесь НЕ перехватывается
console.error("Внешнее обещание", ошибка)
Что такое цепочка промисов в JavaScript?
Вы только что узнали, что ад обратных вызовов указывает на неуправляемые вложенные уровни обратных вызовов. С учетом сказанного, один из способов решить проблему — сделать обратные вызовы НЕ вложенными. Сделайте его мелким!
Сцепление промисов в JavaScript — это когда несколько методов then
и catch
вызываются последовательно для полного удаления вложенных уровней, сохраняя при этом предполагаемые результаты. Можно сказать, что это один из способов рефакторинга вашей кодовой базы.
```javascript
// Цепочка обещаний
первое обещание
.then(() => второе обещание)
.then(() => третье обещание)
.потом(...)
.ловить(...)
.ловить(...)
.ловить(...)
.потом(...)
// Типичный ад обратного вызова, который включает несколько промисов JavaScript
первое обещание
.затем (второе обещание
.затем(третье обещание
.тогда(...).поймать(...)
).ловить(...)
).ловить(...)
Если вы имеете дело с обычными функциями на основе обратного вызова (НЕ промисами), вам нужно сначала промисифицировать функции, чтобы применить цепочку промисов. Есть способы сделать это, например, с помощью библиотеки es6-promisify.
Как работает цепочка промисов в JavaScript?
then
иcatch
являются методами объектаPromise
, поэтому для создания цепочки обратный вызов в методеthen
должен возвращать новыйPromise
.
```javascript
// Правильная реализация
// "() => что-то" является сокращением для "() => {возвратить что-то})
const secondPromise = firstPromise.then(() => newPromise)
secondPromise.then(() => другоеPromise).then(...)
// Неправильная реализация и возникает исключение
firstPromise.then(() => null).then(...)
- Результат обещания переносится на следующее
then
.
```javascript
getWeatherTodayPromise
.then(weatherForecastResult => writeWeatherForecastToLogFilePromise(weatherForecastResult))
// writeWeatherForecastToLogFilePromise(weatherForecastResult), если он выполнен, выдаст "ioWriteResult"
.then(ioWriteResult => Promise.all([
другое обещание (ioWriteResult),
andSomethingElsePromise(),
.затем(списокРезультатов => ...)
- Мы можем вручную инициировать и вернуть новый объект
Promise
, чтобы сформировать цепочку обещаний. Вместо того, чтобы выполнять задачи в одном обратном вызове, вы можете применить эту технику для сегментации кодовой базы на более мелкие фрагменты.
```javascript
первое обещание
.тог(() => {
const isSuccess = synchronousOperation() // логическое значение
вернуть успех? Promise.resolve("Успех") : Promise.reject(новая ошибка("404"))
.then((результат) => console.log(результат)) // Печатаем "Успех"
.catch(error) => console.error(error)) // Вывести ошибку с сообщением «404»
- Метод
then
имеет второй и необязательный аргументonRejectedCallback
, но поскольку мы его не используем, всякий раз, когда возникает исключение, браузер просматривает всю цепочку промисов, чтобы найти первый приемлемыйcatch
для данного ошибка.
Используя цепочку промисов, вы можете иметь одно внешнее «всеобъемлющее» решение, такое как try-catch
, поэтому больше нет возможности проглотить ошибку. У вас может быть условный оператор в одном catch
для нескольких ошибок, или вы можете разделить их на несколько отдельных catch
, как показано ниже.
```javascript
отклонено5xxPromise
.catch(HTTP 4xx) // Браузер: "Не здесь"
.catch(HTTP 5xx) // Браузер: "Хорошо, это перехватывает ошибку 5xx"
.catch(Другие непредвиденные ошибки) // Пропустить
- Вы можете связать
then
послеcatch
. Это означает «всегда действовать, несмотря ни на что».
```javascript
отклонено5xxPromise
.поймать (HTTP 5xx)
.then(console.log("Эта строка всегда выводится"))
Заворачивать
Цепочка обещаний JavaScript — это простая, но мощная функция для решения распространенной проблемы с вложенными обратными вызовами (ад обратных вызовов). Чтобы связать обещания, нужно помнить два основных момента.
- Несколько последовательностей
then
иcatch
могут быть вызваны последовательно, например,promise.then(...).then(...).catch(...).catch(...)
.
- Обратный вызов в методе then должен возвращать новый объект Promise, чтобы можно было продолжить цепочку.
Это правило также относится к TypeScript. В ES2016 (он же ES7) была введена функция async/await, которая делает нашу жизнь еще проще. Тем не менее, если вы по какой-то причине не можете использовать функцию ES7, то цепочка промисов — отличный выбор для рефакторинга вашей кодовой базы.
Первоначально опубликовано в [JavaScript Promise Chaining — Избегайте адского обратного вызова] (https://hungvu.tech/javascript-promise-chaining-avoid-callback-hell).
Заинтересованы в веб-разработке? Другие мои статьи могут быть вам полезны!
Оригинал