Skip to main content

Ланцюжок промісів

Ланцюжок промісів це послідовне виконання асинхронних дій, де результат одного .then() передається наступному. Кожен .then(), .catch() і .finally() повертає новий проміс, і саме це дозволяє вибудувати лінійний потік обробки, без «callback hell».

Теорія

TL;DR

  • Кожен .then() повертає новий проміс, тому виклики складаються в ланцюжок.
  • Значення, повернуте з .then(), стає аргументом наступного .then().
  • Якщо з .then() повернути проміс, ланцюжок чекає на його завершення.
  • Помилка в будь-якому кроці пропускає всі наступні .then() і йде в найближчий .catch().
  • Після .catch() ланцюжок продовжується, бо .catch() теж повертає новий проміс.
  • .finally() виконується завжди і не змінює значення, якщо сам не кидає помилку.

Швидкий приклад

javascript
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); });

Вивід:

text
Step 1, received: 10 Step 2, multiplied: 20 Step 3, total: 25

Кожен .then():

  1. отримує результат попереднього кроку,
  2. повертає новий проміс,
  3. передає його результат далі по ланцюжку.

Як це працює під капотом

  1. Перший проміс створюється і переходить у pending. Коли викликано resolve(value), він стає fulfilled.
  2. Перший .then() отримує value і повертає новий проміс, який тепер чекає на наступний результат.
  3. Кожен .then() у ланцюжку дочекується виконання попереднього, навіть якщо всередині повертається новий проміс.

Приклад з асинхронним кроком:

javascript
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));

Вивід, через секунду:

text
A: 1 B: 2 C: 6

Тут .then() «розпізнає», що йому повернули новий проміс, чекає, поки той завершиться (resolve), і лише потім виконує наступний крок ланцюжка.

Що можна повертати з then()

Що повертає .then()Що станеться
Звичайне значенняЗагортається в Promise.resolve(value)
ПромісЛанцюжок чекає, поки він завершиться
Помилка (throw)Ланцюжок переходить у rejected
Нічого не повертаєПередає undefined далі

Приклад комбінування значень і промісів:

javascript
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().

javascript
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'));

Вивід:

text
Caught: Failure! carrying on

.catch() можна ставити і посеред ланцюжка, щоб відновитися після помилки:

javascript
Promise.reject('Failure') .catch((err) => { console.log('Caught:', err); return 'Recovered'; }) .then((v) => console.log('Next:', v));

Вивід:

text
Caught: Failure Next: Recovered

.catch() теж повертає новий проміс, тому після обробки помилки ланцюжок продовжується.

finally() у ланцюжку

finally() виконується завжди, незалежно від результату, і не змінює значення, якщо не кидає помилку:

javascript
Promise.resolve('OK') .finally(() => console.log('Cleaning up...')) .then(console.log);

Вивід:

text
Cleaning up... OK

Повний ланцюжок з помилкою, відновленням і завершальним кроком:

javascript
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'));

Вивід:

text
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 це може завершити процес.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.