Що таке "callback hell"?
Callback hell - це ситуація, коли в коді використовується багато вкладених callback-функцій, через що код стає:
- важким для читання,
- складним для підтримки,
- і практично неможливим для налагодження.
Простіше кажучи:
"Callback hell" - це пекло для розробника, коли колбеки викликають інші колбеки, утворюючи «піраміду жаху».
Приклад «пекла з колбеків»
getUser('tim', (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
saveToDB(comments, (result) => {
console.log('Готово!');
});
});
});
});Тут:
- Кожна функція чекає завершення попередньої,
- Колбеки вкладені один в одного,
- Відступи ростуть праворуч,
- Помилки складно ловити,
- І код перетворюється на «ліс» із дужок.
Це і є callback hell.
Чому це погано
| Проблема | Опис |
|---|---|
| Погана читабельність | Вкладені рівні роблять код візуально важким |
| Складно обробляти помилки | Кожному колбеку потрібна своя перевірка if (err) ... |
| Залежності ланцюжком | Кожна операція чекає попередню |
| Складне налагодження | Винятки губляться, stack trace довгий |
| Неможливо повторно використати код | Все "зашите" у вкладені анонімні функції |
Звідки він береться
Проблема характерна для асинхронного JavaScript часів до ES6, коли все будувалося на колбеках:
- Робота з API:
XMLHttpRequest - Таймери:
setTimeout - Події:
addEventListener - Node.js до Promise API
Як виглядав старий асинхронний код
fs.readFile('user.json', (err, data) => {
if (err) throw err;
getPosts(JSON.parse(data).id, (err, posts) => {
if (err) throw err;
sendEmail(posts, (err) => {
if (err) throw err;
console.log('Email надіслано!');
});
});
});Все працює, але виглядає жахливо
- складно підтримувати, тестувати і розширювати.
Як уникнути callback hell
1. Використовувати іменовані функції
Це хоча б "випрямляє" код:
function handleUser(user) {
getPosts(user.id, handlePosts);
}
function handlePosts(posts) {
getComments(posts[0].id, handleComments);
}
function handleComments(comments) {
console.log('Готово!');
}
getUser('tim', handleUser);2. Використовувати Promise
getUser('tim')
.then(getPosts)
.then(getComments)
.then(saveToDB)
.then(() => console.log('Готово!'))
.catch(console.error);Код став лінійним, читається зверху вниз, без вкладеності.
3. Використовувати async / await
async function run() {
try {
const user = await getUser('tim');
const posts = await getPosts(user.id);
const comments = await getComments(posts[0].id);
await saveToDB(comments);
console.log('Готово!');
} catch (err) {
console.error(err);
}
}
run();Тепер код виглядає як синхронний, але працює асинхронно. Жодних «пірамід жаху».
Проста аналогія
Callback hell - це як готувати вечерю, де кожен крок вимагає дзвінка другу, а той дзвонить ще комусь, і всі чекають один одного, поки не вийде «піраміда дзвінків».
ПІДСУМОК
| Термін | Опис |
|---|---|
| Callback hell | Ситуація з глибокою вкладеністю callback-функцій |
| Причина | Асинхронний код, побудований на колбеках |
| Недоліки | Погана читабельність, складне налагодження, дублювання логіки |
| Рішення | Promise, async/await, іменовані функції, декомпозиція |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.