Колбэки и ад колбэков
Разберитесь в асинхронном коде на основе колбэков, выявляйте проблему вложенности, известную как ад колбэков, и поймите, зачем были введены Promise для её решения.
«Колбэки и ад колбэков» — бесплатный урок Frontend Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Frontend Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Frontend Academy содержит 4 уроков всего.
Что такое функции обратного вызова
Функция обратного вызова — это функция, переданная в качестве аргумента другой функции, чтобы позднее вызвать её после завершения некоторой работы. Функции обратного вызова — исходный шаблон асинхронности в JavaScript; они появились раньше промисов.
// Synchronous callback:
[1, 2, 3].forEach(function(n) {
console.log(n);
});
// Async callback:
setTimeout(function() {
console.log('1 second later');
}, 1000);Функции обратного вызова Node.js с ошибкой первым аргументом
Node.js стандартизировал соглашение: функции обратного вызова получают ошибку первым аргументом, а результат — вторым. Если значение ошибки равно null или undefined, операция завершилась успешно.
fs.readFile('data.json', 'utf8', function(err, data) {
if (err) {
console.error('Failed to read file:', err);
return;
}
console.log('File contents:', data);
});Асинхронные шаги с функциями обратного вызова
Когда одна асинхронная операция зависит от результата другой, функции обратного вызова вкладываются друг в друга. Уже три последовательных шага начинают выглядеть запутанно.
getUser(userId, function(err, user) {
if (err) return handleError(err);
getOrders(user.id, function(err, orders) {
if (err) return handleError(err);
getInvoice(orders[0].id, function(err, invoice) {
if (err) return handleError(err);
// Finally use invoice
renderInvoice(invoice);
});
});
});Ад функций обратного вызова — пирамида обречённости
Глубокая вложенность называется адом функций обратного вызова или пирамидой обречённости. Проблемы таковы: с каждым уровнем код смещается вправо, обработка ошибок повторяется, логику трудно проследить, а реорганизация кода становится мучительной.
Проблемы ада функций обратного вызова
1) Обработка ошибок требует явной проверки условия ошибки на каждом уровне. 2) Отладка затруднена: трассировки стека не показывают логическую цепочку. 3) Код трудно читать из-за горизонтальных уровней вложенности. 4) Ошибки легко случайно пропустить.
Способ уменьшить вложенность: именованные функции
Вынесите вложенные функции обратного вызова в именованные функции верхнего уровня. Это уменьшает вложенность и позволяет проверять каждый шаг изолированно, но не решает основную проблему координации.
function onInvoice(err, invoice) {
if (err) return handleError(err);
renderInvoice(invoice);
}
function onOrders(err, orders) {
if (err) return handleError(err);
getInvoice(orders[0].id, onInvoice);
}
function onUser(err, user) {
if (err) return handleError(err);
getOrders(user.id, onOrders);
}
getUser(userId, onUser);Библиотеки управления потоком (историческая справка)
Библиотеки вроде async.js предоставляли вспомогательные средства (async.waterfall, async.parallel) для управления потоками на основе функций обратного вызова. До появления промисов в ES2015 это считалось передовым подходом.
Почему функции обратного вызова не исчезают
Функции обратного вызова по-прежнему встречаются повсюду: обработчики событий, методы массивов, setTimeout и API потоков используют их. Главное, что для асинхронного выполнения последовательности промисы и асинхронные функции с ожиданием удобнее. Для простых одноразовых событий функции обратного вызова по-прежнему подходят.
Преобразование функций обратного вызова в промисы
util.promisify() в Node.js оборачивает функции с ошибкой первым аргументом в версии, возвращающие промисы. Это связывает устаревшие API с современным кодом асинхронных функций и ожидания.
const { promisify } = require('util');
const readFile = promisify(require('fs').readFile);
async function readConfig() {
const data = await readFile('config.json', 'utf8');
return JSON.parse(data);
}Когда функции обратного вызова по-прежнему подходят
Обработчики событий (addEventListener) ожидают функции обратного вызова, и это нормально: они срабатывают многократно. Обработчики данных потоков также ожидают функции обратного вызова. Промисы представляют одноразовые асинхронные результаты, а функции обратного вызова — подписки на множество событий.
Дальнейший путь: промисы и асинхронное ожидание
Промисы, появившиеся в ES2015, и асинхронное ожидание, появившееся в ES2017, решили проблему ада функций обратного вызова. Современный код JavaScript почти всегда использует асинхронные функции с ожиданием для последовательных асинхронных операций. Понимать функции обратного вызова по-прежнему важно для чтения старого кода и внутреннего устройства библиотек.
Быстрая проверка
Какую основную проблему описывает выражение «ад функций обратного вызова»?
Итоги: функции обратного вызова
Функции обратного вызова передаются другим функциям, чтобы вызвать их позднее. Это исходный шаблон асинхронности. Функции обратного вызова с ошибкой первым аргументом (ошибка, результат) — соглашение Node.js. Глубокая вложенность создаёт ад функций обратного вызова. Вынесение кода в именованные функции уменьшает вложенность. Для последовательных асинхронных потоков используйте промисы и асинхронные функции с ожиданием. Функции обратного вызова по-прежнему подходят для обработчиков событий и потоков.
Часто задаваемые вопросы
Урок «Колбэки и ад колбэков» бесплатный?
Да — полный текст урока «Колбэки и ад колбэков» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Frontend Academy, подпишись на CoddyKit PRO. Курс Frontend Academy содержит 4 уроков всего.
Чему я научусь в уроке «Колбэки и ад колбэков»?
Разберитесь в асинхронном коде на основе колбэков, выявляйте проблему вложенности, известную как ад колбэков, и поймите, зачем были введены Promise для её решения. Ты практикуешь Frontend Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Frontend Academy?
Предыдущий опыт не требуется. Frontend Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Колбэки и ад колбэков»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Frontend Academy?
Да. Каждый урок Frontend Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Цикл событий: стек вызовов, очередь и микрозадачи
- Колбэки и ад колбэков
- Promise: then, catch, finally и Promise.all
- async/await и обработка ошибок