Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Проміси в JavaScript та методи promise». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Promise** - це об'єкт JavaScript, що представляє майбутній результат асинхронної операції. ```javascript const p = new Promise((resolve, reject) => { setTimeout(() => resolve("done"), 1000); }); p.then(console.log).catch(console.error); // "done" ``` **Ключове:** три стани (pending, fulfilled, rejected). Після завершення стан не змінюється.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Promise** - це об'єкт JavaScript, що представляє майбутній результат асинхронної операції: успіх або помилку. ## Теорія ### TL;DR - Аналогія: замовлення їжі з доставкою. Отримуєш талон (Promise) одразу. Потім або їжа приїжджає (fulfilled), або приходить повідомлення «немає в наявності» (rejected). Одна відповідь, один раз. - Головна відмінність від callback: Promise є одним об'єктом з ланцюжком `.then()`. Callback-функції вкладаються одна в одну, утворюючи глибоко вкладений код. - Стани фінальні: після того як Promise завершився (fulfilled або rejected), він більше не змінюється. - Правила вибору методу: `Promise.all()` коли все має успішно завершитись, `Promise.allSettled()` для часткового успіху, `Promise.race()` для найшвидшого результату, `Promise.any()` для першого успішного. ### Швидкий приклад ```javascript // Імітує API-запит const fetchData = new Promise((resolve, reject) => { setTimeout(() => { const ok = Math.random() > 0.5; ok ? resolve("Дані завантажено") : reject("Мережева помилка"); }, 1000); }); // Споживаємо Promise fetchData .then(result => console.log(result)) // "Дані завантажено" .catch(error => console.error(error)); // "Мережева помилка" ``` Через 1 секунду спрацьовує рівно одна гілка. Promise завершується одного разу і залишається в цьому стані. ### Стани Promise Promise завжди перебуває в одному з трьох станів: - **Pending**: асинхронна операція ще виконується, результату немає - **Fulfilled**: викликано `resolve(value)`, операція успішна - **Rejected**: викликано `reject(error)`, операція завершилась помилкою Pending є початковим станом. Fulfilled і rejected є фінальними. На відміну від браузерних подій, Promise спрацьовує рівно один раз і залишається завершеним. ### Головна відмінність від callback Callback-функції вкладаються. Кожен новий асинхронний крок додає ще один рівень відступу, і обробка помилок потрібна на кожному рівні окремо. Promise ланцюгується: кожен `.then()` повертає новий Promise, тому рівень відступу залишається одним. Помилки автоматично «падають» до найближчого `.catch()`. Саме це усуває callback hell для будь-якого коду глибше одного асинхронного кроку. ### Методи Promise **Promise.all()** Запускає всі проміси паралельно і чекає поки кожен з них завершиться успішно. Повертає масив результатів у тому ж порядку, що й вхідний масив. Якщо хоча б один проміс відхиляється, весь результат одразу відхиляється з цією помилкою. Решта промісів продовжують виконуватись, але їхні результати ігноруються. ```javascript const [user, posts] = await Promise.all([ fetch('/api/user/1').then(r => r.json()), fetch('/api/posts?userId=1').then(r => r.json()) ]); // Обидва запити йдуть паралельно; якщо один впав, результату немає ``` Використовуй коли всі операції мають успішно завершитись перед тим як продовжити роботу. **Promise.allSettled()** Те саме паралельне виконання, але чекає поки кожен проміс завершиться незалежно від результату. Повертає об'єкт для кожного: `{status: 'fulfilled', value: ...}` або `{status: 'rejected', reason: ...}`. ```javascript const results = await Promise.allSettled([fetchUser(), fetchPosts()]); const loaded = results .filter(r => r.status === 'fulfilled') .map(r => r.value); ``` Підходить коли частковий успіх прийнятний: дашборд, що показує ті дані, які вдалось завантажити. **Promise.race()** Повертає перший проміс, що завершився, незалежно від того, успішно чи з помилкою. Решта ігноруються. Найпоширеніший кейс — патерн timeout: ```javascript const withTimeout = (promise, ms) => Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject('Timeout'), ms)) ]); ``` Якщо всі вхідні проміси залишаються в стані pending, `Promise.race()` теж залишається pending назавжди. **Promise.any()** Повертає перший проміс, що завершився **успішно**. Відхилення пропускаються. Якщо всі відхиляються, кидає `AggregateError`. З'явився в ES2021, тому перевір чи потрібен поліфіл для старих середовищ. ```javascript // Використовуємо той CDN, що відповів першим const resource = await Promise.any([ fetch('https://cdn1.example.com/lib.js'), fetch('https://cdn2.example.com/lib.js'), fetch('https://cdn3.example.com/lib.js') ]); ``` ### Як це працює всередині Коли `resolve()` або `reject()` спрацьовує, Promise позначає себе завершеним і додає обробники `.then()` або `.catch()` до черги мікрозавдань (microtask queue). Мікрозавдання виконуються після поточного синхронного коду, але до того як браузер перемалює сторінку або спрацює наступний `setTimeout`. Тому `Promise.resolve().then(fn)` завжди виконується раніше `setTimeout(fn, 0)`: різні черги, різний пріоритет. ### Типові помилки **1. Відсутній `return` в `.then()` з фігурними дужками** ```javascript // Неправильно - результат губиться, наступний .then() отримує undefined fetch('/api/data') .then(response => { response.json(); // немає return }) .then(data => console.log(data)); // undefined // Правильно fetch('/api/data') .then(response => response.json()) .then(data => console.log(data)); ``` Стрілкова функція з `{}` не повертає значення автоматично. **2. Відсутній `.catch()` в кінці ланцюжка** ```javascript // Неправильно - помилки зникають, застосунок продовжує роботу в зламаному стані fetch('/api').then(r => r.json()).then(showData); // Правильно fetch('/api').then(r => r.json()).then(showData).catch(console.error); ``` **3. Очікування, що `Promise.all()` чекає всіх перед відхиленням** На практиці це помилка, яку я бачив найчастіше на код-рев'ю. `Promise.all()` зупиняється при першому відхиленні і не чекає повільніших промісів. ```javascript Promise.all([ Promise.resolve('OK'), Promise.reject('BOOM'), // відхиляється одразу slowPromise // результат ігнорується ]).catch(err => console.log(err)); // "BOOM" миттєво ``` Якщо потрібні всі результати, використовуй `Promise.allSettled()`. **4. Обгортання в `new Promise()` коду, що вже повертає Promise** ```javascript // Неправильно - зайві накладні витрати function loadUser() { return new Promise(resolve => resolve(fetch('/api/user'))); } // Правильно - fetch вже повертає Promise function loadUser() { return fetch('/api/user'); } ``` ### Де використовується - React `useEffect`: `fetch('/api/data').then(r => r.json()).then(setData).catch(setError)` - Express middleware: `Promise.all([db.query(...), cache.get(...)])` для паралельного отримання даних - Redux Toolkit: `createAsyncThunk` керує lifecycle Promise всередині - Next.js `getServerSideProps`: `Promise.all([fetchUser(), fetchPosts()])` для паралельних SSR-запитів ### Питання на співбесіді **Q:** Яка різниця між `Promise.all()` і `Promise.allSettled()`? **A:** `Promise.all()` відхиляється, як тільки один проміс відхиляється. `Promise.allSettled()` чекає завершення всіх і повертає об'єкт для кожного з полями `status` та `value` або `reason`. Раннього виходу немає. **Q:** Чому `Promise.resolve().then(fn)` виконується раніше `setTimeout(fn, 0)`? **A:** Callback `.then()` потрапляє до черги мікрозавдань. `setTimeout` потрапляє до черги макрозавдань. Після кожного завдання рушій виконує всі мікрозавдання перед тим як узяти наступне макрозавдання. Мікрозавдання йдуть першими. **Q:** Що повертає `Promise.all([])` при порожньому масиві? **A:** Одразу виконується з `[]`. Немає промісів, немає чого чекати. **Q:** Чи може Promise виконатись з іншим Promise як значенням? **A:** Так. Якщо викликати `resolve(anotherPromise)`, зовнішній Promise приймає стан внутрішнього і чекає поки він завершиться. Будь-який об'єкт з методом `.then()` (так зване "thenable") поводиться так само. **Q:** Що повертає `Promise.race()` якщо всі вхідні проміси назавжди залишаються в pending? **A:** Він теж залишається в pending назавжди. Нічого його не завершить. ## Приклади ### Завантаження профілю користувача з обробкою помилок ```javascript function loadUserProfile(userId) { return fetch(`/api/users/${userId}`) .then(response => { if (!response.ok) throw new Error('Користувача не знайдено'); return response.json(); }) .catch(error => { console.error('Помилка завантаження:', error.message); return null; // graceful fallback }); } loadUserProfile(1).then(profile => { if (profile) renderProfile(profile); }); ``` Throw всередині `.then()` працює так само як `reject()`: помилка потрапляє до найближчого `.catch()`. ### Паралельні запити для дашборду ```javascript async function loadDashboard(userId) { try { const [user, posts, notifications] = await Promise.all([ fetch(`/api/users/${userId}`).then(r => r.json()), fetch(`/api/posts?author=${userId}`).then(r => r.json()), fetch(`/api/notifications/${userId}`).then(r => r.json()) ]); return { user, posts, notifications }; } catch (error) { console.error('Дашборд не завантажився:', error); return null; } } ``` Три запити відправляються одночасно. Загальний час дорівнює часу найповільнішого, а не сумі всіх трьох. ### Крайній випадок з Promise.all та відхиленням ```javascript const tasks = [ Promise.resolve('Швидкий'), new Promise(resolve => setTimeout(() => resolve('Повільний'), 100)), Promise.reject('БАХ!'), // відхиляється одразу new Promise(resolve => setTimeout(() => resolve('Ніколи не буде'), 200)) ]; Promise.all(tasks) .then(results => console.log(results)) .catch(err => console.log('Провалено:', err)); // "Провалено: БАХ!" - без затримки ``` Proміс на 200мс ніколи не доставляє результат. `Promise.all()` вже відхилено. Якщо потрібні всі результати, заміни на `Promise.allSettled()` і фільтруй за полем `status`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.