Как избавиться от вложенных коллбэков: рассматриваем на примере приготовления гамбургера
.png)
Адаптированный перевод статьи Zell Liew «How to deal with nested callbacks and avoid callback hell».
В JavaScript есть странные вещи. Одна из них — обратный вызов, который находится в обратном вызове, который находится в обратном вызове. На английском языке это называется callback hell или ад обратных вызовов. Статья поможет справиться с этой проблемой и писать код понятнее.
Что такое обратные вызовы: функции, которые выполняются после выполнения других функций
Обратный вызов или коллбэк (англ. callback) — функция, которая выполняется после выполнения другой функции. Если вы не понимаете, о чем речь, прочитайте статью об обратных вызовах, а потом возвращайтесь к этому материалу.
В чём проблема вложенных коллбэков: код становится непонятным
Вложенные обратные вызовы выглядят так:
За такие конструкции мы любим недолюбливаем JavaScript. Вложенные коллбэки буквально сбивают с толку и взрывают мозг. Но эта проблема решается.
Как избегать вложенных вызовов: четыре рецепта
Сначала просто посмотрим на варианты решения проблемы без углубления в детали. Вот четыре способа вырваться из ада вложенных коллбэков:
- Комментируйте код.
- Разделяйте большие функции на несколько маленьких.
- Используйте промисы.
- Используйте async/await.
Но прежде чем разбираться с каждым способом, давайте создадим свой callback hell. Это нужно, чтобы почувствовать боль вложенных обратных вызовов.
Делаем больно: откуда берутся вложенные коллбэки
Представьте, что готовите гамбургер. Вот алгоритм приготовления:
- Купить ингредиенты.
- Приготовить говядину.
- Достать булочки.
- Положить говядину между булочками.
- Подать гамбургер.
Давайте опишем приготовление гамбургера с помощью JavaScript:
Теперь представьте, что учите ребенка делать гамбургер. Вы инструктируете маленького помощника, то есть объясняете ему каждый шаг алгоритма. После каждой инструкции вы ждете, пока маленький повар выполнит её. Только после этого вы переходите к следующему шагу.
В JavaScript ожидание выражается с помощью коллбэков. Чтобы приготовить гамбургер, сначала нам надо отварить говядину. Но мы сможем положить мясо в кастрюлю только после того, как купим его в магазине.
Теперь смешиваем русский язык и JavaScript. Чтобы приготовить говядину, нужно передать аргумент beef
в функцию cookBeef
. Если не сделать этого, функции cookBeef
будет нечего готовить.
Когда говядина готова, можем достать булочки.
Когда это сделано, можем положить мясо и остальную начинку между булочками.
Наконец гамбургер готов. Но мы пока не можем вернуть burger
из makeBurger
, так как функция асинхронная. Чтобы подать блюдо на стол, нужно использовать обратный вызов.
Ну что, еще хотите гамбургер, или вложенные коллбэки испортили аппетит и настроение? Давайте посмотрим на способы решения проблемы, может, это вас тонизирует.
Первое решение: комментируйте код
Функцию makeBurger
из примера выше легко понять, так как это достаточно простой случай. Эта функция просто не очень элегантная. Но новичка она может заставить задуматься. Если вы недавно изучаете программирование и сразу не понимаете, что происходит в коде, постарайтесь объяснить происходящее с помощью комментариев.
Комментарии объясняют вам или другому разработчику, что происходит в коде. Кто-то благодаря этим пояснениям поймет функцию и не бросит программирование.
Также полезно:
Что такое callback-функция в JavaScript?
Второе решение: разделяйте большие функции на несколько маленьких
В функции makeBurger
из нашего примера этот подход уже реализован. Разберем этот момент подробнее.
Чтобы выполнить getBeef
, первый коллбэк, вам надо сходить за говядиной в магазин. Ну, или пойти на кухню и достать мясо из холодильника. Представьте, что на кухне два холодильника. Нужно выбрать правильный — тот, в котором лежит говядина. Выразим это с помощью кода.
Чтобы приготовить говядину, нужно положить её в духовку, установить температуру 200 °C и включить таймер на 20 минут. Вот код:
Теперь представьте, что эти шаги описываются в функции makeBurger
. У вас не просто пропадет аппетит. Вы возненавидите гамбургеры и JavaScript.
Запомните этот принцип: функции с вложенными коллбэками надо разделять на несколько маленьких функций.
Третье решение: используйте промисы
Если вы не изучали промисы, это можно сделать в курсе «Асинхронное программирование».
Промисы решают проблему вложенных обратных вызовов. Посмотрите на код:
Алгоритм приготовления гамбургера можно выразить ещё проще, если использовать промисы с одним аргументом:
Код стал понятнее. Давайте посмотрим, как превратить функцию с вложенными коллбэками в промисы.
Как заменить функцию с коллбэками на промисы
Чтобы решить задачу, нужно вместо каждого обратного вызова записать промис. Когда коллбэк успешно завершается, промис выполняется с результатом resolve
. Если коллбэк не выполняется, получаем ошибку reject
.
Если вы используете Node, все функции с обратными вызовами имеют одинаковый синтаксис:
- Коллбэк передается в качестве последнего аргумента.
- Коллбэк всегда принимает два аргумента.
- Аргументы принимаются в одном порядке: сначала обработка ошибки, затем выполнение действия.
Одинаковый синтаксис коллбэков позволяет использовать библиотеки типа ES6 Promisify или Denodeify. Если вы пользуетесь версией Node 8.0 и выше, можно применять библиотеку util.promisify. Эти инструменты конвертируют коллбэки в промисы.
Четвертое решение: используйте асинхронные функции
Чтобы использовать четвертое решение, нужно понимать следующие вещи:
- Как превращать коллбэки в промисы. Этому посвящен предыдущий раздел.
- Как работать с асинхронными функциями. Информацию можно найти в курсе «Асинхронное программирование», ссылка выше.
Давайте посмотрим на асинхронную функцию makeBurger
.
Этот код можно улучшить. Представьте, что у вас два маленьких помощника. Один из них выполняет действие getBuns
, то есть достает булочки. Второй выполняет getBeef
, то есть достает из холодильника говядину. Вы ждете (await
) каждого помощника (Promise.all
).
Да, то же самое можно записать с помощью промисов. Но синтаксис async/await более понятный.
Завершаем: рассмотрели четыре способа решения проблемы вложенных коллбэков
Вот эти решения:
- Писать комментарии.
- Разделять большие функции на несколько маленьких.
- Использовать промисы.
- Использовать async/await.
Оригинал публикации: How to deal with nested callbacks and avoid callback hell. Мнение автора оригинальной статьи может не совпадать с позицией редакции.
Дмитрий Дементий
6 лет назад