Ланцюжок промісів
Ланцюжок промісів це послідовне виконання асинхронних дій, де результат одного .then() передається наступному. Кожен .then(), .catch() і .finally() повертає новий проміс, і саме це дозволяє вибудувати лінійний потік обробки, без «callback hell».
Теорія
TL;DR
- Кожен
.then()повертає новий проміс, тому виклики складаються в ланцюжок. - Значення, повернуте з
.then(), стає аргументом наступного.then(). - Якщо з
.then()повернути проміс, ланцюжок чекає на його завершення. - Помилка в будь-якому кроці пропускає всі наступні
.then()і йде в найближчий.catch(). - Після
.catch()ланцюжок продовжується, бо.catch()теж повертає новий проміс. .finally()виконується завжди і не змінює значення, якщо сам не кидає помилку.
Швидкий приклад
new Promise((resolve) => {
setTimeout(() => resolve(10), 1000);
})
.then((num) => {
console.log('Step 1, received:', num);
return num * 2;
})
.then((num) => {
console.log('Step 2, multiplied:', num);
return num + 5;
})
.then((result) => {
console.log('Step 3, total:', result);
});Вивід:
Step 1, received: 10
Step 2, multiplied: 20
Step 3, total: 25Кожен .then():
- отримує результат попереднього кроку,
- повертає новий проміс,
- передає його результат далі по ланцюжку.
Як це працює під капотом
- Перший проміс створюється і переходить у
pending. Коли викликаноresolve(value), він стаєfulfilled. - Перший
.then()отримуєvalueі повертає новий проміс, який тепер чекає на наступний результат. - Кожен
.then()у ланцюжку дочекується виконання попереднього, навіть якщо всередині повертається новий проміс.
Приклад з асинхронним кроком:
Promise.resolve(1)
.then((n) => {
console.log('A:', n);
return new Promise((resolve) =>
setTimeout(() => resolve(n + 1), 1000)
);
})
.then((n) => {
console.log('B:', n);
return n * 3;
})
.then((n) => console.log('C:', n));Вивід, через секунду:
A: 1
B: 2
C: 6Тут .then() «розпізнає», що йому повернули новий проміс, чекає, поки той завершиться (resolve), і лише потім виконує наступний крок ланцюжка.
Що можна повертати з then()
Що повертає .then() | Що станеться |
|---|---|
| Звичайне значення | Загортається в Promise.resolve(value) |
| Проміс | Ланцюжок чекає, поки він завершиться |
Помилка (throw) | Ланцюжок переходить у rejected |
| Нічого не повертає | Передає undefined далі |
Приклад комбінування значень і промісів:
Promise.resolve('A')
.then((v) => v + 'B') // returns 'AB'
.then((v) => Promise.resolve(v + 'C')) // waits for another promise
.then((v) => v + 'D')
.then(console.log); // ABCDПомилки в ланцюжку
Якщо в будь-якому .then() станеться помилка (throw або reject), усі наступні .then() пропускаються, і керування переходить у найближчий .catch().
Promise.resolve(10)
.then((x) => x / 2)
.then(() => {
throw new Error('Failure!');
})
.then(() => console.log('this will not run'))
.catch((err) => console.error('Caught:', err.message))
.then(() => console.log('carrying on'));Вивід:
Caught: Failure!
carrying on.catch() можна ставити і посеред ланцюжка, щоб відновитися після помилки:
Promise.reject('Failure')
.catch((err) => {
console.log('Caught:', err);
return 'Recovered';
})
.then((v) => console.log('Next:', v));Вивід:
Caught: Failure
Next: Recovered.catch() теж повертає новий проміс, тому після обробки помилки ланцюжок продовжується.
finally() у ланцюжку
finally() виконується завжди, незалежно від результату, і не змінює значення, якщо не кидає помилку:
Promise.resolve('OK')
.finally(() => console.log('Cleaning up...'))
.then(console.log);Вивід:
Cleaning up...
OKПовний ланцюжок з помилкою, відновленням і завершальним кроком:
Promise.resolve(1)
.then((x) => x + 1)
.then(() => {
throw new Error('Oops!');
})
.catch((e) => {
console.log('Error caught:', e.message);
return 5;
})
.then((x) => console.log('Result:', x))
.finally(() => console.log('Done'));Вивід:
Error caught: Oops!
Result: 5
DoneАналогія: ланцюжок промісів це конвеєр. Кожен .then() це станція, де деталь обробляють; якщо щось пішло не так, .catch() лагодить помилку і повертає деталь на конвеєр; наприкінці .finally() вимикає верстати і підмітає підлогу.
Підсумок
| Особливість | Опис |
|---|---|
Кожен .then() | Повертає новий проміс |
| Значення йдуть вниз | Результат із .then() іде в наступний |
| Помилки | Летять вниз до найближчого .catch() |
.finally() | Виконується в будь-якому разі |
| Ланцюжок | Дозволяє писати асинхронний код лінійно, без вкладеності |
Типові помилки
- Забути
returnу.then(). Без нього наступний крок отримаєundefined, а ланцюжок не чекатиме вкладений проміс. - Вкладати
.then()один в одний. Це відтворює callback hell, який ланцюжок мав прибрати; повертайте проміс замість вкладення. - Ставити
.catch()лише на початку. Він ловить те, що сталося вище за нього, помилки з наступних кроків пройдуть повз. - Думати, що
.catch()перериває ланцюжок. Після нього проміс знову успішний, і наступні.then()виконаються. - Вважати, що синхронний код у
.then()виконається одразу. Обробники завжди запускаються як мікрозадачі, після поточного синхронного коду. - Губити помилки без фінального
.catch(). Ланцюжок без нього дає unhandled rejection, а в Node.js це може завершити процес.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.