Callback-функции
Callback — функция, которая передаётся другой функции и вызывается позже. Самый старый способ работы с асинхронностью в JavaScript.
Простой callback
function greet(name, callback) {
console.log(`Привет, ${name}`);
callback();
}
greet('Алиса', () => {
console.log('Callback вызван');
});
Асинхронный callback
function loadData(callback) {
setTimeout(() => {
const data = { id: 1, name: 'Алиса' };
callback(data);
}, 1000);
}
loadData((data) => {
console.log(data);
});
loadData не возвращает данные сразу. Она вызывает callback, когда данные готовы.
Callback с ошибкой
Стандартный паттерн Node.js — error-first callback:
function loadUser(id, callback) {
setTimeout(() => {
if (id <= 0) {
callback(new Error('Неверный ID'), null);
return;
}
callback(null, { id, name: 'Алиса' });
}, 1000);
}
loadUser(1, (error, user) => {
if (error) {
console.error(error);
return;
}
console.log(user);
});
Первым аргументом всегда идёт ошибка (или null), вторым — результат.
Встроенные примеры
setTimeout, addEventListener, методы массивов:
setTimeout(() => console.log('позже'), 1000);
button.addEventListener('click', () => console.log('клик'));
[1, 2, 3].forEach((x) => console.log(x));
Проблема: callback hell
Когда много вложенных callback-ов — код превращается в «пирамиду»:
loadUser(1, (err, user) => {
if (err) return handleError(err);
loadPosts(user.id, (err, posts) => {
if (err) return handleError(err);
loadComments(posts[0].id, (err, comments) => {
if (err) return handleError(err);
loadAuthor(comments[0].authorId, (err, author) => {
if (err) return handleError(err);
console.log(author);
});
});
});
});
Проблемы:
- Тяжело читать
- Сложно обрабатывать ошибки
- Легко забыть
return - Сложно рефакторить
Решение: промисы
loadUser(1)
.then((user) => loadPosts(user.id))
.then((posts) => loadComments(posts[0].id))
.then((comments) => loadAuthor(comments[0].authorId))
.then((author) => console.log(author))
.catch(handleError);
Плоско, читаемо, одна точка обработки ошибок.
Когда callbacks всё ещё нужны
addEventListener— реакция на события- Методы массивов:
forEach,map,filter setTimeout- Библиотеки, не поддерживающие промисы
- Node.js API с error-first
Преобразование в промис
function loadUserPromise(id) {
return new Promise((resolve, reject) => {
loadUser(id, (err, user) => {
if (err) reject(err);
else resolve(user);
});
});
}
await loadUserPromise(1);
Такой процесс называется промисификацией.
Итоги
- Callback — функция, вызываемая позже
- Error-first — стандарт Node.js
- Вложенные callbacks — «callback hell»
- Промисы решают проблему
addEventListenerи методы массивов всё ещё на callbacks