HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p><em>Адаптированный перевод статьи Zell Liew "How to deal with nested callbacks and avoid callback hell".</em></p>
1 <p><em>Адаптированный перевод статьи Zell Liew "How to deal with nested callbacks and avoid callback hell".</em></p>
2 <p>В JavaScript есть странные вещи. Одна из них - обратный вызов, который находится в обратном вызове, который находится в обратном вызове. На английском языке это называется callback hell или ад обратных вызовов. Статья поможет справиться с этой проблемой и писать код понятнее.</p>
2 <p>В JavaScript есть странные вещи. Одна из них - обратный вызов, который находится в обратном вызове, который находится в обратном вызове. На английском языке это называется callback hell или ад обратных вызовов. Статья поможет справиться с этой проблемой и писать код понятнее.</p>
3 <h2>Содержание</h2>
3 <h2>Содержание</h2>
4 <ul><li><a>Что такое обратные вызовы: функции, которые выполняются после выполнения других функций</a></li>
4 <ul><li><a>Что такое обратные вызовы: функции, которые выполняются после выполнения других функций</a></li>
5 <li><a>В чём проблема вложенных коллбэков: код становится непонятным</a></li>
5 <li><a>В чём проблема вложенных коллбэков: код становится непонятным</a></li>
6 <li><a>Как избегать вложенных вызовов: четыре рецепта</a></li>
6 <li><a>Как избегать вложенных вызовов: четыре рецепта</a></li>
7 <li><a>Делаем больно: откуда берутся вложенные коллбэки</a></li>
7 <li><a>Делаем больно: откуда берутся вложенные коллбэки</a></li>
8 <li><a>Первое решение: комментируйте код</a></li>
8 <li><a>Первое решение: комментируйте код</a></li>
9 <li><a>Второе решение: разделяйте большие функции на несколько маленьких</a></li>
9 <li><a>Второе решение: разделяйте большие функции на несколько маленьких</a></li>
10 <li><a>Третье решение: используйте промисы</a></li>
10 <li><a>Третье решение: используйте промисы</a></li>
11 <li><a>Как заменить функцию с коллбэками на промисы</a></li>
11 <li><a>Как заменить функцию с коллбэками на промисы</a></li>
12 <li><a>Четвертое решение: используйте асинхронные функции</a></li>
12 <li><a>Четвертое решение: используйте асинхронные функции</a></li>
13 <li><a>Завершаем: рассмотрели четыре способа решения проблемы вложенных коллбэков</a></li>
13 <li><a>Завершаем: рассмотрели четыре способа решения проблемы вложенных коллбэков</a></li>
14 </ul><h2>Что такое обратные вызовы: функции, которые выполняются после выполнения других функций</h2>
14 </ul><h2>Что такое обратные вызовы: функции, которые выполняются после выполнения других функций</h2>
15 <p>Обратный вызов или коллбэк (англ. callback) - функция, которая выполняется после выполнения другой функции. Если вы не понимаете, о чем речь, прочитайте<a>статью об обратных вызовах</a>, а потом возвращайтесь к этому материалу.</p>
15 <p>Обратный вызов или коллбэк (англ. callback) - функция, которая выполняется после выполнения другой функции. Если вы не понимаете, о чем речь, прочитайте<a>статью об обратных вызовах</a>, а потом возвращайтесь к этому материалу.</p>
16 <h2>В чём проблема вложенных коллбэков: код становится непонятным</h2>
16 <h2>В чём проблема вложенных коллбэков: код становится непонятным</h2>
17 <p>Вложенные обратные вызовы выглядят так:</p>
17 <p>Вложенные обратные вызовы выглядят так:</p>
18 <p>За такие конструкции мы любим недолюбливаем JavaScript. Вложенные коллбэки буквально сбивают с толку и взрывают мозг. Но эта проблема решается.</p>
18 <p>За такие конструкции мы любим недолюбливаем JavaScript. Вложенные коллбэки буквально сбивают с толку и взрывают мозг. Но эта проблема решается.</p>
19 <h2>Как избегать вложенных вызовов: четыре рецепта</h2>
19 <h2>Как избегать вложенных вызовов: четыре рецепта</h2>
20 <p>Сначала просто посмотрим на варианты решения проблемы без углубления в детали. Вот четыре способа вырваться из ада вложенных коллбэков:</p>
20 <p>Сначала просто посмотрим на варианты решения проблемы без углубления в детали. Вот четыре способа вырваться из ада вложенных коллбэков:</p>
21 <ol><li>Комментируйте код.</li>
21 <ol><li>Комментируйте код.</li>
22 <li>Разделяйте большие функции на несколько маленьких.</li>
22 <li>Разделяйте большие функции на несколько маленьких.</li>
23 <li>Используйте промисы.</li>
23 <li>Используйте промисы.</li>
24 <li>Используйте async/await.</li>
24 <li>Используйте async/await.</li>
25 </ol><p>Но прежде чем разбираться с каждым способом, давайте создадим свой callback hell. Это нужно, чтобы почувствовать боль вложенных обратных вызовов.</p>
25 </ol><p>Но прежде чем разбираться с каждым способом, давайте создадим свой callback hell. Это нужно, чтобы почувствовать боль вложенных обратных вызовов.</p>
26 <h2>Делаем больно: откуда берутся вложенные коллбэки</h2>
26 <h2>Делаем больно: откуда берутся вложенные коллбэки</h2>
27 <p>Представьте, что готовите гамбургер. Вот алгоритм приготовления:</p>
27 <p>Представьте, что готовите гамбургер. Вот алгоритм приготовления:</p>
28 <ol><li>Купить ингредиенты.</li>
28 <ol><li>Купить ингредиенты.</li>
29 <li>Приготовить говядину.</li>
29 <li>Приготовить говядину.</li>
30 <li>Достать булочки.</li>
30 <li>Достать булочки.</li>
31 <li>Положить говядину между булочками.</li>
31 <li>Положить говядину между булочками.</li>
32 <li>Подать гамбургер.</li>
32 <li>Подать гамбургер.</li>
33 </ol><p>Давайте опишем приготовление гамбургера с помощью JavaScript:</p>
33 </ol><p>Давайте опишем приготовление гамбургера с помощью JavaScript:</p>
34 <p>Теперь представьте, что учите ребенка делать гамбургер. Вы инструктируете маленького помощника, то есть объясняете ему каждый шаг алгоритма. После каждой инструкции вы ждете, пока маленький повар выполнит её. Только после этого вы переходите к следующему шагу.</p>
34 <p>Теперь представьте, что учите ребенка делать гамбургер. Вы инструктируете маленького помощника, то есть объясняете ему каждый шаг алгоритма. После каждой инструкции вы ждете, пока маленький повар выполнит её. Только после этого вы переходите к следующему шагу.</p>
35 <p>В JavaScript ожидание выражается с помощью коллбэков. Чтобы приготовить гамбургер, сначала нам надо отварить говядину. Но мы сможем положить мясо в кастрюлю только после того, как купим его в магазине.</p>
35 <p>В JavaScript ожидание выражается с помощью коллбэков. Чтобы приготовить гамбургер, сначала нам надо отварить говядину. Но мы сможем положить мясо в кастрюлю только после того, как купим его в магазине.</p>
36 <p>Теперь смешиваем русский язык и JavaScript. Чтобы приготовить говядину, нужно передать аргумент beef в функцию cookBeef. Если не сделать этого, функции cookBeef будет нечего готовить. Когда говядина готова, можем достать булочки.</p>
36 <p>Теперь смешиваем русский язык и JavaScript. Чтобы приготовить говядину, нужно передать аргумент beef в функцию cookBeef. Если не сделать этого, функции cookBeef будет нечего готовить. Когда говядина готова, можем достать булочки.</p>
37 <p>Когда это сделано, можем положить мясо и остальную начинку между булочками.</p>
37 <p>Когда это сделано, можем положить мясо и остальную начинку между булочками.</p>
38 <p>Наконец гамбургер готов. Но мы пока не можем вернуть burger из makeBurger, так как функция асинхронная. Чтобы подать блюдо на стол, нужно использовать обратный вызов.</p>
38 <p>Наконец гамбургер готов. Но мы пока не можем вернуть burger из makeBurger, так как функция асинхронная. Чтобы подать блюдо на стол, нужно использовать обратный вызов.</p>
39 <p>Ну что, еще хотите гамбургер, или вложенные коллбэки испортили аппетит и настроение? Давайте посмотрим на способы решения проблемы, может, это вас тонизирует.</p>
39 <p>Ну что, еще хотите гамбургер, или вложенные коллбэки испортили аппетит и настроение? Давайте посмотрим на способы решения проблемы, может, это вас тонизирует.</p>
40 <h2>Первое решение: комментируйте код</h2>
40 <h2>Первое решение: комментируйте код</h2>
41 <p>Функцию makeBurger из примера выше легко понять, так как это достаточно простой случай. Эта функция просто не очень элегантная. Но новичка она может заставить задуматься. Если вы недавно изучаете программирование и сразу не понимаете, что происходит в коде, постарайтесь объяснить происходящее с помощью комментариев.</p>
41 <p>Функцию makeBurger из примера выше легко понять, так как это достаточно простой случай. Эта функция просто не очень элегантная. Но новичка она может заставить задуматься. Если вы недавно изучаете программирование и сразу не понимаете, что происходит в коде, постарайтесь объяснить происходящее с помощью комментариев.</p>
42 <p>Комментарии объясняют вам или другому разработчику, что происходит в коде. Кто-то благодаря этим пояснениям поймет функцию и не бросит программирование.</p>
42 <p>Комментарии объясняют вам или другому разработчику, что происходит в коде. Кто-то благодаря этим пояснениям поймет функцию и не бросит программирование.</p>
43 <h2>Второе решение: разделяйте большие функции на несколько маленьких</h2>
43 <h2>Второе решение: разделяйте большие функции на несколько маленьких</h2>
44 <p>В функции makeBurger из нашего примера этот подход уже реализован. Разберем этот момент подробнее. Чтобы выполнить getBeef, первый коллбэк, вам надо сходить за говядиной в магазин. Ну, или пойти на кухню и достать мясо из холодильника. Представьте, что на кухне два холодильника. Нужно выбрать правильный - тот, в котором лежит говядина. Выразим это с помощью кода.</p>
44 <p>В функции makeBurger из нашего примера этот подход уже реализован. Разберем этот момент подробнее. Чтобы выполнить getBeef, первый коллбэк, вам надо сходить за говядиной в магазин. Ну, или пойти на кухню и достать мясо из холодильника. Представьте, что на кухне два холодильника. Нужно выбрать правильный - тот, в котором лежит говядина. Выразим это с помощью кода.</p>
45 <p>Чтобы приготовить говядину, нужно положить её в духовку, установить температуру 200 °C и включить таймер на 20 минут. Вот код:</p>
45 <p>Чтобы приготовить говядину, нужно положить её в духовку, установить температуру 200 °C и включить таймер на 20 минут. Вот код:</p>
46 <p>Теперь представьте, что эти шаги описываются в функции makeBurger. У вас не просто пропадет аппетит. Вы возненавидите гамбургеры и JavaScript.</p>
46 <p>Теперь представьте, что эти шаги описываются в функции makeBurger. У вас не просто пропадет аппетит. Вы возненавидите гамбургеры и JavaScript.</p>
47 <p>Запомните этот принцип: функции с вложенными коллбэками надо разделять на несколько маленьких функций.</p>
47 <p>Запомните этот принцип: функции с вложенными коллбэками надо разделять на несколько маленьких функций.</p>
48 <h2>Третье решение: используйте промисы</h2>
48 <h2>Третье решение: используйте промисы</h2>
49 <p>Если вы не изучали промисы, это можно сделать в курсе<a>"Асинхронное программирование"</a>.</p>
49 <p>Если вы не изучали промисы, это можно сделать в курсе<a>"Асинхронное программирование"</a>.</p>
50 <p>Промисы решают проблему вложенных обратных вызовов. Посмотрите на код:</p>
50 <p>Промисы решают проблему вложенных обратных вызовов. Посмотрите на код:</p>
51 <p>Алгоритм приготовления гамбургера можно выразить ещё проще, если использовать промисы с одним аргументом:</p>
51 <p>Алгоритм приготовления гамбургера можно выразить ещё проще, если использовать промисы с одним аргументом:</p>
52 <p>Код стал понятнее. Давайте посмотрим, как превратить функцию с вложенными коллбэками в промисы.</p>
52 <p>Код стал понятнее. Давайте посмотрим, как превратить функцию с вложенными коллбэками в промисы.</p>
53 <h2>Как заменить функцию с коллбэками на промисы</h2>
53 <h2>Как заменить функцию с коллбэками на промисы</h2>
54 <p>Чтобы решить задачу, нужно вместо каждого обратного вызова записать промис. Когда коллбэк успешно завершается, промис выполняется с результатом resolve. Если коллбэк не выполняется, получаем ошибку reject.</p>
54 <p>Чтобы решить задачу, нужно вместо каждого обратного вызова записать промис. Когда коллбэк успешно завершается, промис выполняется с результатом resolve. Если коллбэк не выполняется, получаем ошибку reject.</p>
55 <p>Если вы используете Node, все функции с обратными вызовами имеют одинаковый синтаксис:</p>
55 <p>Если вы используете Node, все функции с обратными вызовами имеют одинаковый синтаксис:</p>
56 <ol><li>Коллбэк передается в качестве последнего аргумента.</li>
56 <ol><li>Коллбэк передается в качестве последнего аргумента.</li>
57 <li>Коллбэк всегда принимает два аргумента.</li>
57 <li>Коллбэк всегда принимает два аргумента.</li>
58 <li>Аргументы принимаются в одном порядке: сначала обработка ошибки, затем выполнение действия.</li>
58 <li>Аргументы принимаются в одном порядке: сначала обработка ошибки, затем выполнение действия.</li>
59 </ol><p>Одинаковый синтаксис коллбэков позволяет использовать библиотеки типа<a>ES6 Promisify</a>или<a>Denodeify</a>. Если вы пользуетесь версией Node 8.0 и выше, можно применять библиотеку<a>util.promisify</a>. Эти инструменты конвертируют коллбэки в промисы.</p>
59 </ol><p>Одинаковый синтаксис коллбэков позволяет использовать библиотеки типа<a>ES6 Promisify</a>или<a>Denodeify</a>. Если вы пользуетесь версией Node 8.0 и выше, можно применять библиотеку<a>util.promisify</a>. Эти инструменты конвертируют коллбэки в промисы.</p>
60 <h2>Четвертое решение: используйте асинхронные функции</h2>
60 <h2>Четвертое решение: используйте асинхронные функции</h2>
61 <p>Чтобы использовать четвертое решение, нужно понимать следующие вещи:</p>
61 <p>Чтобы использовать четвертое решение, нужно понимать следующие вещи:</p>
62 <ol><li>Как превращать коллбэки в промисы. Этому посвящен предыдущий раздел.</li>
62 <ol><li>Как превращать коллбэки в промисы. Этому посвящен предыдущий раздел.</li>
63 <li>Как работать с асинхронными функциями. Информацию можно найти в курсе "Асинхронное программирование", ссылка выше.</li>
63 <li>Как работать с асинхронными функциями. Информацию можно найти в курсе "Асинхронное программирование", ссылка выше.</li>
64 </ol><p>Давайте посмотрим на асинхронную функцию makeBurger.</p>
64 </ol><p>Давайте посмотрим на асинхронную функцию makeBurger.</p>
65 <p>Этот код можно улучшить. Представьте, что у вас два маленьких помощника. Один из них выполняет действие getBuns, то есть достает булочки. Второй выполняет getBeef, то есть достает из холодильника говядину. Вы ждете (await) каждого помощника (Promise.all).</p>
65 <p>Этот код можно улучшить. Представьте, что у вас два маленьких помощника. Один из них выполняет действие getBuns, то есть достает булочки. Второй выполняет getBeef, то есть достает из холодильника говядину. Вы ждете (await) каждого помощника (Promise.all).</p>
66 <p>Да, то же самое можно записать с помощью промисов. Но синтаксис async/await более понятный.</p>
66 <p>Да, то же самое можно записать с помощью промисов. Но синтаксис async/await более понятный.</p>
67 <h2>Завершаем: рассмотрели четыре способа решения проблемы вложенных коллбэков</h2>
67 <h2>Завершаем: рассмотрели четыре способа решения проблемы вложенных коллбэков</h2>
68 <p>Вот эти решения:</p>
68 <p>Вот эти решения:</p>
69 <ol><li>Писать комментарии.</li>
69 <ol><li>Писать комментарии.</li>
70 <li>Разделять большие функции на несколько маленьких.</li>
70 <li>Разделять большие функции на несколько маленьких.</li>
71 <li>Использовать промисы.</li>
71 <li>Использовать промисы.</li>
72 <li>Использовать async/await.</li>
72 <li>Использовать async/await.</li>
73 </ol><p><em>Оригинал публикации:<a>How to deal with nested callbacks and avoid callback hell</a>. Мнение автора оригинальной статьи может не совпадать с позицией редакции.</em></p>
73 </ol><p><em>Оригинал публикации:<a>How to deal with nested callbacks and avoid callback hell</a>. Мнение автора оригинальной статьи может не совпадать с позицией редакции.</em></p>