Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Callback hell». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Callback hell виникає тоді, коли кілька асинхронних операцій виконуються вкладено одна в одну і кожна наступна залежить від результату попередньої: код перетворюється на «ліс» вкладених функцій, що зсувається вправо драбинкою.** Такий код важко читати й налагоджувати, у ньому складно обробляти помилки (кожен рівень потребує власної перевірки) і майже неможливо перевикористовувати окремі кроки. Розв'язок це Promise і `async/await`, які записують ту саму послідовність пласко, з одним спільним `try/catch`. ```javascript // було: вкладеність росте з кожним кроком getUser(id, user => { getPosts(user.id, posts => { getComments(posts[0].id, comments => { saveToFile(comments, () => console.log('Готово!')); }); }); }); // стало: лінійний код const user = await getUser(id); const posts = await getPosts(user.id); const comments = await getComments(posts[0].id); await saveToFile(comments); ``` **Ключове:** проблема не в самих колбеках, а в глибокій вкладеності і некерованому потоці виконання; Promise і `async/await` роблять потік лінійним.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Callback hell це стан коду, коли кілька асинхронних операцій виконуються вкладено одна в одну, бо кожна наступна залежить від результату попередньої, і програма перетворюється на «ліс» з вкладених функцій.** Такий код важко читати й налагоджувати, у ньому складно обробляти помилки і його важко змінювати або перевикористовувати. ## Теорія ### TL;DR - Причина: послідовні асинхронні кроки, де кожен наступний потребує результату попереднього. - Симптом: код зсувається вправо «драбинкою», рівень вкладеності росте з кожним кроком. - Наслідки: погана читабельність, дублювання обробки помилок, неможливість перевикористати окремий крок. - Проблема не в колбеках як таких, а в глибокій вкладеності і некерованому потоці виконання. - Лікування: Promise і `async/await`, які записують ту саму послідовність лінійно. ### Швидкий приклад ```javascript getUser(id, user => { getPosts(user.id, posts => { getComments(posts[0].id, comments => { saveToFile(comments, () => { console.log('Готово!'); }); }); }); }); ``` Чотири кроки, і вже чотири рівні вкладеності. Додайте п'ятий, і читати це стане ще важче. ### Чому з'являється вкладеність Callback не повертає значення викликачу: результат приходить пізніше, всередину переданої функції. Тому єдиний спосіб використати цей результат у наступному кроці, це написати наступний виклик **усередині** колбека. Кожна залежність між кроками додає рівень вкладеності, і послідовність з N операцій дає N рівнів відступів. Так лінійна за змістом логіка («візьми користувача, потім його пости, потім коментарі, потім збережи») перетворюється на пірамідальну за формою. Цю форму і називають pyramid of doom. ### Чим це шкідливо - **Читабельність.** Щоб зрозуміти порядок кроків, треба читати структуру вкладеності, а не послідовність рядків. Кінець кожного кроку губиться серед закриваючих дужок. - **Обробка помилок.** Немає спільного місця для збоїв: у стилі error-first кожен рівень має власний параметр `err`, і перевірку доводиться повторювати знову й знову. - **Зміни і перевикористання.** Вирізати один крок або переставити кроки місцями важко: вони фізично вкладені один в одний, а не стоять поруч. ```javascript getUser(id, (err, user) => { if (err) return handle(err); getPosts(user.id, (err, posts) => { if (err) return handle(err); getComments(posts[0].id, (err, comments) => { if (err) return handle(err); // корисна робота ховається під трьома перевірками }); }); }); ``` ### Як розплутати: Promise і async/await Promise повертає значення викликачу, тому наступний крок можна поставити **поруч**, а не всередині. Ланцюжок `.then` уже робить код пласким, а `async/await` дає майже синхронний вигляд: ```javascript const user = await getUser(id); const posts = await getPosts(user.id); const comments = await getComments(posts[0].id); await saveToFile(comments); ``` Той самий код з обробкою помилок в одному місці: ```javascript async function run(id) { try { const user = await getUser(id); const posts = await getPosts(user.id); const comments = await getComments(posts[0].id); await saveToFile(comments); console.log('Готово!'); } catch (err) { console.error(err.message); } } ``` Якщо кроки незалежні, їх не треба вишиковувати послідовно: `Promise.all` запускає їх паралельно і чекає всі одразу. ```javascript const [user, settings] = await Promise.all([getUser(id), getSettings(id)]); ``` Коли API старе і вміє тільки колбеки, його загортають у Promise один раз (`util.promisify` у Node.js або власна обгортка), і далі весь код пишеться лінійно. ### Типові помилки - Вважати, що колбеки самі по собі погані. Погана саме глибока вкладеність; `map`, `forEach` і обробники подій це теж колбеки, і з ними все гаразд. - Обробляти помилку тільки на найглибшому рівні: збій на першому кроці просто загубиться. - «Виправляти» вкладеність винесенням кожного колбека в іменовану функцію: рівнів стає менше візуально, але потік виконання все одно розсипаний по файлу. - Ставити `await` у циклі для незалежних операцій: послідовність стає повільнішою там, де мав бути `Promise.all`. - Забувати, що всередині колбека `return` повертає значення не з зовнішньої функції, а з самого колбека.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.