Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке callback hell». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Callback hell - це ситуація, коли асинхронний код складається з багатьох вкладених одна в одну callback-функцій: кожна наступна операція стартує всередині callback попередньої.** Відступи ростуть праворуч, помилку доводиться перевіряти на кожному рівні окремо, stack trace стає марним, а логіку неможливо перевикористати, бо вона «зашита» у вкладені анонімні функції. Перший крок лікування - винести callback в іменовані функції, повний - перейти на Promise або async/await. ```javascript // замість піраміди callback: лінійний код const user = await getUser('tim'); const posts = await getPosts(user.id); ``` **Ключове:** проблема не в самих callback, а у вкладеності; лінійний ланцюжок Promise чи async/await прибирає її разом із дублюванням обробки помилок.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Callback hell - це ситуація, коли в коді використовується багато вкладених одна в одну callback-функцій.** Через це код стає важко читати, важко підтримувати і практично неможливо налагоджувати: callback викликають інші callback і утворюють «піраміду жаху». ## Теорія ### TL;DR - Callback hell виникає, коли кожна наступна асинхронна операція запускається всередині callback попередньої. - Візуально це піраміда: відступи ростуть праворуч, а закривні дужки збираються у довгий хвіст. - Головні проблеми: погана читабельність, дубльована обробка помилок, жорсткий зв'язок кроків, неінформативний stack trace. - Проблема характерна для асинхронного JavaScript часів до ES6: `XMLHttpRequest`, `setTimeout`, `addEventListener`, ранній Node.js. - Лікується декомпозицією на іменовані функції, а повністю - переходом на Promise і async/await. ### Швидкий приклад ```javascript // класична «піраміда приреченості» getUser('tim', (user) => { getPosts(user.id, (posts) => { getComments(posts[0].id, (comments) => { saveToDB(comments, (result) => { console.log('Готово!'); }); }); }); }); ``` Тут кожна функція чекає на завершення попередньої, callback-функції вкладені одна в одну, відступи ростуть праворуч, помилки складно ловити, а код перетворюється на «ліс» із дужок. Це і є callback hell. ### Чому це погано | Проблема | Опис | | --- | --- | | **Погана читабельність** | Рівні вкладеності роблять код візуально важким | | **Складно обробляти помилки** | Кожному callback потрібна власна перевірка `if (err) ...` | | **Залежності ланцюжком** | Кожна операція чекає на попередню | | **Складне налагодження** | Винятки губляться, stack trace довгий і нічого не пояснює | | **Код неможливо перевикористати** | Уся логіка «зашита» у вкладені анонімні функції | ### Звідки він береться Проблема характерна для асинхронного JavaScript часів до ES6, коли все будувалося на callback: - робота з мережею: `XMLHttpRequest`; - таймери: `setTimeout`; - події: `addEventListener`; - Node.js до появи Promise API. Ось як виглядав типовий старий асинхронний код: ```javascript 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('Лист надіслано!'); }); }); }); ``` Усе працює, але виглядає жахливо: такий код важко підтримувати, тестувати й розширювати. ### Як уникнути callback hell #### 1. Іменовані функції Найдешевший крок: винести кожен callback в окрему функцію з іменем. Це хоча б «випрямляє» код. ```javascript 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 ```javascript getUser('tim') .then(getPosts) .then(getComments) .then(saveToDB) .then(() => console.log('Готово!')) .catch(console.error); ``` Код став лінійним, читається згори вниз, без вкладеності, а одного `.catch()` вистачає на весь ланцюжок. #### 3. async / await ```javascript 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-функцій | | **Причина** | Асинхронний код, побудований на callback | | **Недоліки** | Погана читабельність, складне налагодження, дублювання логіки | | **Рішення** | Promise, async/await, іменовані функції, декомпозиція | ### Типові помилки - Думати, що callback hell - це про обсяг коду. Проблема саме у вкладеності та жорсткому зв'язку кроків, а не в кількості рядків. - Переписати піраміду на Promise, але залишити вкладеність: викликати новий `.then()` усередині попереднього замість того, щоб повернути Promise назовні. - Забути `return` у `.then()`: наступний крок отримає `undefined`, і ланцюжок мовчки «розсиплеться». - Обробляти помилку в кожному callback окремо замість одного `.catch()` або одного `try/catch` навколо `await`. - Ставити `await` у циклі там, де операції незалежні: лінійний код стає повільним, тут потрібен `Promise.all`. - Вважати, що async/await прибирає callback. Він їх лише ховає: під капотом усе одно Promise і черга мікрозадач.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.