Promise представляє значення, яке може ще не бути доступним. Він перебуває в одному з трьох станів: **pending** (початковий), **fulfilled** (виконаний зі значенням) або **rejected** (відхилений з причиною).
```javascript
const p = new Promise((resolve, reject) => {
// виконавець запускається синхронно
if (Math.random() > 0.5) {
resolve('успіх'); // перехід до fulfilled
} else {
reject(new Error('невдача')); // перехід до rejected
}
});
p.then(value => console.log(value)) // 'успіх'
.catch(err => console.log(err.message)); // 'невдача'
```
**Черга мікрозадач**
Зворотні виклики Promise (`.then`, `.catch`, `.finally`) не запускаються одразу — вони потрапляють до *черги мікрозадач* і виконуються після завершення поточного синхронного коду, але перед наступною макрозадачею (setTimeout, setInterval, I/O):
```javascript
console.log('1');
Promise.resolve().then(() => console.log('2'));
console.log('3');
// Вивід: 1 3 2
// '2' виконується після '3', оскільки зворотні виклики .then є мікрозадачами
```
**Ланцюжок then**
`.then()` повертає новий Promise. Їх можна ланцюжком, щоб виконувати кроки послідовно:
```javascript
fetchUser(id)
.then(user => fetchPosts(user.id)) // ОБОВ'ЯЗКОВО повертати наступний Promise
.then(posts => render(posts))
.catch(err => showError(err));
```
**Найпоширеніша помилка в ланцюжках: відсутній return**
Якщо забути `return` всередині `.then`, ланцюжок не чекає на внутрішній Promise — він виконується з `undefined`, і наступний `.then` спрацьовує одразу з `undefined`:
```javascript
// Помилка: відсутній return
fetchUser(id)
.then(user => {
fetchPosts(user.id); // забули 'return'!
})
.then(posts => {
console.log(posts); // undefined — fetchPosts не очікувався
});
// Виправлено:
fetchUser(id)
.then(user => fetchPosts(user.id)) // стрілка без фігурних дужок неявно повертає
.then(posts => console.log(posts));
```
**Створення вже виконаних/відхилених Promise**
```javascript
Promise.resolve(42).then(v => console.log(v)); // 42
Promise.reject(new Error('ой')).catch(e => console.log(e.message)); // 'ой'
```
Зручні для огортання синхронних значень у Promise-сумісний інтерфейс.
async/await: обробка помилок, необроблене відхилення, послідовно vs паралельно
Функції `async` завжди повертають Promise. `await` призупиняє виконання всередині async-функції до виконання Promise — навколишній код не блокується.
```javascript
async function getUser(id) {
const user = await fetchUser(id); // чекає fetchUser
const posts = await fetchPosts(user.id); // потім чекає fetchPosts
return { user, posts };
}
// async-функція повертає Promise
getUser(1).then(data => console.log(data));
```
**Обробка помилок з async/await**
Використовуй `try/catch` навколо викликів `await` — це перехоплює і синхронні throw, і відхилені Promise:
```javascript
async function loadData(id) {
try {
const data = await fetchData(id);
return process(data);
} catch (err) {
console.error('Помилка:', err.message);
return null;
} finally {
hideLoadingSpinner(); // завжди виконується
}
}
```
**Необроблене відхилення**
Якщо відхилений Promise не має `.catch` і немає навколишнього `try/catch`, він стає необробленим відхиленням. У Node.js це завершує процес; у браузерах спрацьовує `window.unhandledrejection`:
```javascript
// Небезпечно: немає обробки помилок
async function bad() {
const data = await fetchData(); // якщо відхилено, помилка зникає мовчки
return data;
}
// Краще: завжди обробляй
bad().catch(err => console.error(err));
```
**Послідовне vs паралельне виконання**
Два `await` підряд виконуються послідовно (другий чекає першого). Щоб виконувати паралельно, запусти обидва Promise *перед* очікуванням будь-якого:
```javascript
// Послідовно: загальний час = час(A) + час(B)
async function sequential() {
const a = await fetchA();
const b = await fetchB(); // починається лише після завершення fetchA
return [a, b];
}
// Паралельно: загальний час = max(час(A), час(B))
async function parallel() {
const [a, b] = await Promise.all([fetchA(), fetchB()]);
return [a, b];
}
```
**Змішування async/await з .then**
Їх можна змішувати — `await` працює з будь-яким thenable, а результат async-функції можна ланцюжком через `.then`:
```javascript
const result = await someAsyncFn().then(v => transform(v));
```
Але змішування стилів в одному ланцюжку знижує читабельність. Перевагу надавай одному стилю.
**Promise.all** — всі мають успішно завершитися, інакше негайна відмова:
```javascript
const [user, settings, feed] = await Promise.all([
fetchUser(id),
fetchSettings(id),
fetchFeed(id),
]);
// Якщо будь-який відхилено, все негайно відхиляється.
// Інші продовжують виконуватися, але їхні результати ігноруються.
```
**Promise.allSettled** — чекати всіх незалежно від успіху/невдачі:
```javascript
const results = await Promise.allSettled([
fetchUser(id),
fetchSettings(id),
]);
results.forEach(r => {
if (r.status === 'fulfilled') console.log(r.value);
else console.error(r.reason);
});
// Використовуй, коли потрібні всі результати, навіть часткові.
```
**Promise.race** — виконується, як тільки перший завершується:
```javascript
const result = await Promise.race([
fetchData(),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000)),
]);
// Поширене використання: додати тайм-аут до будь-якої async-операції
```
**Promise.any** (ES2021) — виконується за першим *успішним* результатом, ігнорує відхилення:
```javascript
const data = await Promise.any([
fetchFromCDN1(),
fetchFromCDN2(),
fetchFromCDN3(),
]);
// Використовуй для резервних запитів: перший CDN, що відповість, перемагає.
// Відхиляється з AggregateError лише якщо ВСІ відхилені.
```
**Порівняння комбінаторів**
```
Promise.all - всі успішні або негайна відмова. Результат: масив значень
Promise.allSettled - чекає всіх. Результат: масив {status, value/reason}
Promise.race - перший виконаний перемагає (успіх або відмова)
Promise.any - перший успішний перемагає; відмова лише якщо всі відхилені
```
**Послідовна async-ітерація з for...of**
Коли потрібно обробляти елементи по одному (наприклад, обмеження API), використовуй `for...of` — `map` з `Promise.all` запустив би всі запити паралельно:
```javascript
async function processSequentially(items) {
const results = [];
for (const item of items) {
const result = await processItem(item); // чекає кожного
results.push(result);
}
return results;
}
```
Async-патерни: повтор, обмеження паралельності, тайм-аут, черга, кеш
Напишіть функцію fetchUser(id), яка повертає Promise, що виконується через 500 мс з об'єктом-заглушкою { id, name }. Якщо id дорівнює 0, відхилити з повідомленням 'Invalid ID'.
function fetchUser(id) {
}
fetchUser(1).then(user => console.log(user));
fetchUser(0).catch(err => console.log(err));
Рішення
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id === 0) {
reject('Invalid ID');
} else {
resolve({ id, name: `User ${id}` });
}
}, 500);
});
}
fetchUser(1).then(user => console.log(user));
fetchUser(0).catch(err => console.log(err));
Перепишіть наступний ланцюжок Promise за допомогою async/await. Функція має отримати користувача, потім вивести його ім'я. Обробляйте помилки через try/catch.
Оригінал: fetchUser(id).then(u => console.log(u.name)).catch(e => console.log('Error:', e))
// Припускається, що fetchUser(id) повертає Promise (з попереднього завдання)
async function showUserName(id) {
}
showUserName(1);
showUserName(0);
Напишіть async-функцію fetchAll(ids), яка приймає масив ID користувачів, паралельно отримує всіх користувачів через Promise.all і повертає масив об'єктів користувачів.
// Припускається, що fetchUser(id) доступна
async function fetchAll(ids) {
}
fetchAll([1, 2, 3]).then(users => console.log(users));
Напишіть async-функцію fetchWithResults(ids), яка паралельно намагається отримати всіх користувачів і повертає масив об'єктів { id, user } для успішних запитів і { id, error } для невдалих. Використайте Promise.allSettled.
async function fetchWithResults(ids) {
}
fetchWithResults([1, 0, 2]).then(results => console.log(results));
Напишіть async-функцію withTimeout(promise, ms), яка змагає заданий проміс із таймаутом. Якщо проміс не виконується протягом ms мілісекунд, відхилити з 'Timeout'.
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function withTimeout(promise, ms) {
}
// Тест: повільна операція, що займає 2 секунди
const slow = delay(2000).then(() => 'done');
withTimeout(slow, 1000)
.then(r => console.log(r))
.catch(e => console.log(e));
Напишіть async-функцію retry(fn, attempts), яка викликає fn() (що повертає Promise) до `attempts` разів. Якщо fn успішна, повернути результат. Якщо всі спроби зазнали невдачі, викинути останню помилку.
async function retry(fn, attempts) {
}
let callCount = 0;
const flaky = () => new Promise((resolve, reject) => {
callCount++;
if (callCount < 3) reject(new Error(`Attempt ${callCount} failed`));
else resolve('Success!');
});
retry(flaky, 5).then(console.log).catch(console.error);
Рішення
async function retry(fn, attempts) {
let lastError;
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (e) {
lastError = e;
}
}
throw lastError;
}
let callCount = 0;
const flaky = () => new Promise((resolve, reject) => {
callCount++;
if (callCount < 3) reject(new Error(`Attempt ${callCount} failed`));
else resolve('Success!');
});
retry(flaky, 5).then(console.log).catch(console.error);
Перепишіть наступний ланцюжок Promise як async-функцію. Оригінал отримує користувача, потім його пости, потім повертає заголовки всіх постів.
Оригінал (getUser(id) і getUserPosts(userId) повертають Promise):
getUser(id).then(u => getUserPosts(u.id)).then(posts => posts.map(p => p.title))
// Припускається, що getUser(id) і getUserPosts(userId) повертають Promise
async function getUserPostTitles(id) {
}
getUserPostTitles(1).then(titles => console.log(titles));
Напишіть async-функцію pipeline(id), яка: 1) отримує користувача, 2) якщо ім'я починається з 'A', кидає помилку 'Name starts with A', 3) інакше виводить ім'я. Оберніть виклик pipeline() у try/catch, який логує будь-яку помилку.
async function pipeline(id) {
const user = await fetchUser(id);
if (user.name.startsWith('A')) {
throw new Error('Name starts with A');
}
console.log(user.name);
}
try {
await pipeline(1);
} catch (e) {
console.log('Caught:', e.message || e);
}
No split tab
Налаштування cookies
Ми використовуємо необхідні cookies для роботи сайту. З вашого дозволу ми також можемо зберігати налаштування сайту та використовувати аналітичні й рекламні cookies, щоб розуміти використання сайту й підтримувати розвиток проєкту.
* Ви завжди можете змінити свій вибір у налаштуваннях сайту.
Оберіть категорії cookies
Налаштування аналітики
Можна вимкнути аналітику використання платформи. Також можна надіслати в Google Analytics запит на видалення даних про використання цього сайту, пов'язаних із цим браузером.
Навчальний workspace
Навчайтеся, читаючи, запускаючи код і розв'язуючи задачі.
Практикуйте програмування з поясненнями тем, вправами, інструментами browser IDE, перевіркою regex і тренуванням друку коду в одному workspace.
Відкривайте інструменти у вкладках.Вправи, IDE-інструменти й тренажери залишаються доступними як вкладки сайту.
Перемикайтеся без втрати контексту.Переходьте між поясненнями, кодом та утилітами, зберігаючи своє місце.
Використовуйте sidebar як карту.Ліві панелі містять навігацію, налаштування, файли, libraries та керування інструментами.
PythonJavaScriptSQLite
Одна IDE, три практичні режими
Python у браузері.Запускайте невеликі скрипти, пробуйте бібліотеки й тренуйте API-запити без встановлення.
JavaScript для швидких експериментів.Перевіряйте код для браузера й порівнюйте ідеї поруч із навчальними матеріалами.
SQLite для практики з даними.Відкривайте оглядач бази даних, переглядайте таблиці, пишіть запити й вивчайте SQL локально.
ТемаIDE
Працюйте поруч у split tabs
Тримайте інструкції перед очима.Відкрийте вправу або довідкову сторінку поруч з IDE, замість постійних перемикань.
Порівнюйте інструменти під час навчання.Розміщуйте перевірки regex, пояснення та експерименти з кодом поруч, коли це потрібно для задачі.
Закрийте split, коли завершите.Workspace повернеться до однієї сфокусованої вкладки, а відкриті вкладки сайту залишаться доступними.
Тренажер друку коду
Або звичайного тексту
Тренажер розрахований на фізичну клавіатуру.Відкрийте цей розділ на ноутбуці або комп'ютері з широким екраном. На телефоні тренування друку не працюватиме коректно.
Швидкість: 0 зн/хв
0 слів/хв
Найкраща швидкість (60с): 0 зн/хв
0 слів/хв
Помилки: 0
Загальний час: 0.0 с
Щоб тренувати сліпий друк, не підглядайте на фізичну клавіатуру.