Пошук уроків, статей та іншого контенту
Як з'єднувати послідовні асинхронні кроки через then і правильно поширювати помилки одним catch на весь ланцюжок.
Ключова властивість, що уможливлює ланцюжки: then сам повертає новий Promise — тому наступний then можна викликати прямо на результаті попереднього, замість вкладення:
getUser(userId)
.then((user) => getOrders(user.id)) // повертає Promise, наступний then чекає саме на нього
.then((orders) => getOrderDetails(orders[0].id))
.then((details) => console.log(details))
.catch((error) => console.error("Помилка на будь-якому кроці:", error));Порівняйте з callback hell на початку попереднього уроку: та сама послідовність трьох залежних кроків, але без зростаючої вкладеності — кожен крок на одному рівні відступу.
Promise.resolve(5)
.then((n) => n * 2) // повертає звичайне значення — воно автоматично «загортається» в Promise
.then((n) => n + 1)
.then((n) => console.log(n)); // 11Забутий return у then — найпоширеніша помилка при побудові ланцюжка: наступний крок отримає undefined замість очікуваного значення, так само, як забутий return у map (модуль «Об'єкти та масиви»).
Якщо будь-який крок у ланцюжку відхиляється (reject) або кидає виняток, виконання одразу «перестрибує» до найближчого catch далі по ланцюжку, пропускаючи всі проміжні then:
Promise.resolve()
.then(() => {
throw new Error("Помилка на першому кроці");
})
.then(() => console.log("Цей рядок НЕ виконається"))
.then(() => console.log("І цей теж НЕ виконається"))
.catch((error) => console.error("Спіймано:", error.message)); // "Спіймано: Помилка на першому кроці"catch сам повертає Promise — якщо після нього є ще then, ланцюжок продовжується зі значенням, яке catch повернув (або undefined, якщо нічого не повернено):
fetchUserData(-1)
.catch((error) => {
console.error(error.message);
return { id: 0, name: "Гість" }; // запасне значення — ланцюжок продовжується, а не зупиняється
})
.then((user) => console.log(user)); // { id: 0, name: "Гість" }function validateEmail(email) {
return email.includes("@")
? Promise.resolve(email)
: Promise.reject(new Error("Некоректний email"));
}
validateEmail("test@example.com")
.then((email) => email.toLowerCase())
.then((email) => registerUser(email))
.then((user) => console.log("Зареєстровано:", user))
.catch((error) => console.error("Не вдалось зареєструвати:", error.message));Забутий return у середині then, через що наступний крок отримує undefined замість реального значення.
Розміщення catch посередині ланцюжка, а не в кінці — помилки з кроків ПІСЛЯ такого catch залишаться необробленими.
Вкладання then один в одного замість продовження ланцюжка (фактичне повернення до callback hell, лише з іншим синтаксисом).
Кожен then повертає новий Promise, що й дозволяє з'єднувати асинхронні кроки в пласкому, читабельному ланцюжку замість вкладеності. Значення, повернуте з then, стає входом для наступного кроку; помилка з будь-якого кроку «перестрибує» одразу до найближчого catch. Наступний урок покаже async/await — синтаксис, що робить той самий механізм ще ближчим на вигляд до звичайного синхронного коду.
Спробуйте самостійно
Побудуйте ланцюжок із трьох then, де кожен додає 10 до отриманого числа, починаючи з Promise.resolve(0), і виведіть фінальний результат (30) в останньому then.