В GitHub Actions конвейер сборки выглядит так: скачать зависимости → запустить тесты → собрать бандл → задеплоить. Каждый шаг зависит от предыдущего. В JavaScript это точная аналогия цепочки промисов: каждый .then() получает результат предыдущего и передаёт следующему.
Колбэки для последовательных асинхронных операций превращаются в «пирамиду судьбы». Промисы решают это через плоскую цепочку: каждый .then() возвращает новый промис, что позволяет писать последовательный асинхронный код без вложенности.
Promise.resolve(1)
.then(n => n * 2) // получает 1, возвращает 2
.then(n => n + 10) // получает 2, возвращает 12
.then(n => console.log(n)) // 12Каждый .then получает результат предыдущего и передаёт дальше.
Если в .then вернуть промис, следующий .then получит его результат, а не сам промис. Это убирает вложенность:
fetchUser(1)
.then(user => fetchOrders(user.id)) // возвращаем промис
.then(orders => { // получаем уже массив заказов
console.log(orders.length)
}).catch перехватывает ошибки от всех предыдущих шагов цепочки:
fetchUser(1)
.then(user => fetchOrders(user.id))
.then(orders => filterActive(orders))
.then(active => console.log(active))
.catch(err => console.error('Ошибка на любом шаге:', err.message))Бросить ошибку в .then — то же что reject:
.then(user => {
if (user.banned) throw new Error('Пользователь заблокирован')
return user
}).finally запускается независимо от результата — скрыть спиннер, закрыть соединение:
showSpinner()
fetchData()
.then(data => render(data))
.catch(err => showError(err))
.finally(() => hideSpinner()) // выполнится в любом случае// Разница: если в 'a' бросить ошибку — 'b' в первом случае не поймает её
promise.then(onFulfilled, onRejected) // onRejected не ловит ошибки из onFulfilled!
promise
.then(onFulfilled)
.catch(onRejected) // ловит ошибки И от promise, И от onFulfilledПредпочитай .then(a).catch(b).
.catch может не завершить цепочку, а вернуть запасное значение:
fetchUserFromAPI(id)
.catch(() => fetchUserFromCache(id)) // fallback на кеш при ошибке API
.then(user => renderUser(user)) // продолжаем в любом случае
.catch(err => showError(err)) // финальная обработкаОшибка 1: вложение вместо цепочки
// Неправильно — callback hell с промисами
fetchUser(id).then(user => {
fetchOrders(user.id).then(orders => { // вложенный .then!
console.log(orders)
})
})
// Правильно — плоская цепочка через return
fetchUser(id)
.then(user => fetchOrders(user.id)) // return промис
.then(orders => console.log(orders))Ошибка 2: забыли return в .then
// Неправильно — следующий .then получит undefined
fetchUser(id)
.then(user => {
fetchOrders(user.id) // нет return! промис потерян
})
.then(orders => console.log(orders)) // orders === undefined
// Правильно
fetchUser(id)
.then(user => fetchOrders(user.id)) // стрелочная функция с implicit return
.then(orders => console.log(orders))Ошибка 3: нет .catch в конце
// Неправильно — ошибка «проглатывается», ничего не видно
fetchUser(id)
.then(user => renderUser(user))
// Если fetchUser упадёт — UnhandledPromiseRejection, тихий баг
// Правильно
fetchUser(id)
.then(user => renderUser(user))
.catch(err => console.error('Ошибка:', err.message))Конвейер обработки заказа: загрузка → проверка → обогащение данных → форматирование
function simulateFetch(data, delay = 150, shouldFail = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail) reject(new Error('Сетевая ошибка'))
else resolve(data)
}, delay)
})
}
const users = [
{ id: 1, name: 'Иван Петров', active: true },
{ id: 2, name: 'Анна Сидорова', active: false },
{ id: 3, name: 'Олег Козлов', active: true },
]
const orders = [
{ id: 101, userId: 1, total: 2500, items: 3 },
{ id: 102, userId: 3, total: 890, items: 1 },
]
// Цепочка: загрузить пользователей → оставить активных → обогатить заказами → форматировать
simulateFetch(users)
.then(list => list.filter(u => u.active)) // только активные
.then(active => {
const ids = active.map(u => u.id)
const userOrders = orders.filter(o => ids.includes(o.userId))
return simulateFetch(userOrders) // загружаем заказы
})
.then(activeOrders => activeOrders.map(order => ({
...order,
summary: `Заказ #${order.id}: ${order.items} товара на ${order.total}₽`
})))
.then(result => {
console.log('Активные заказы:')
result.forEach(o => console.log(' -', o.summary))
// - Заказ #101: 3 товара на 2500₽
// - Заказ #102: 1 товара на 890₽
})
.catch(err => console.error('Ошибка в конвейере:', err.message))
.finally(() => console.log('Загрузка завершена'))
// Пример восстановления после ошибки (fallback)
const cache = [{ id: 1, name: 'Иван (из кеша)', active: true }]
simulateFetch(null, 100, true) // API упал
.catch(() => simulateFetch(cache, 50)) // берём из кеша
.then(data => console.log('Данные:', data[0].name))
// 'Данные: Иван (из кеша)'
.catch(err => console.error('Даже кеш недоступен:', err.message))В GitHub Actions конвейер сборки выглядит так: скачать зависимости → запустить тесты → собрать бандл → задеплоить. Каждый шаг зависит от предыдущего. В JavaScript это точная аналогия цепочки промисов: каждый .then() получает результат предыдущего и передаёт следующему.
Колбэки для последовательных асинхронных операций превращаются в «пирамиду судьбы». Промисы решают это через плоскую цепочку: каждый .then() возвращает новый промис, что позволяет писать последовательный асинхронный код без вложенности.
Promise.resolve(1)
.then(n => n * 2) // получает 1, возвращает 2
.then(n => n + 10) // получает 2, возвращает 12
.then(n => console.log(n)) // 12Каждый .then получает результат предыдущего и передаёт дальше.
Если в .then вернуть промис, следующий .then получит его результат, а не сам промис. Это убирает вложенность:
fetchUser(1)
.then(user => fetchOrders(user.id)) // возвращаем промис
.then(orders => { // получаем уже массив заказов
console.log(orders.length)
}).catch перехватывает ошибки от всех предыдущих шагов цепочки:
fetchUser(1)
.then(user => fetchOrders(user.id))
.then(orders => filterActive(orders))
.then(active => console.log(active))
.catch(err => console.error('Ошибка на любом шаге:', err.message))Бросить ошибку в .then — то же что reject:
.then(user => {
if (user.banned) throw new Error('Пользователь заблокирован')
return user
}).finally запускается независимо от результата — скрыть спиннер, закрыть соединение:
showSpinner()
fetchData()
.then(data => render(data))
.catch(err => showError(err))
.finally(() => hideSpinner()) // выполнится в любом случае// Разница: если в 'a' бросить ошибку — 'b' в первом случае не поймает её
promise.then(onFulfilled, onRejected) // onRejected не ловит ошибки из onFulfilled!
promise
.then(onFulfilled)
.catch(onRejected) // ловит ошибки И от promise, И от onFulfilledПредпочитай .then(a).catch(b).
.catch может не завершить цепочку, а вернуть запасное значение:
fetchUserFromAPI(id)
.catch(() => fetchUserFromCache(id)) // fallback на кеш при ошибке API
.then(user => renderUser(user)) // продолжаем в любом случае
.catch(err => showError(err)) // финальная обработкаОшибка 1: вложение вместо цепочки
// Неправильно — callback hell с промисами
fetchUser(id).then(user => {
fetchOrders(user.id).then(orders => { // вложенный .then!
console.log(orders)
})
})
// Правильно — плоская цепочка через return
fetchUser(id)
.then(user => fetchOrders(user.id)) // return промис
.then(orders => console.log(orders))Ошибка 2: забыли return в .then
// Неправильно — следующий .then получит undefined
fetchUser(id)
.then(user => {
fetchOrders(user.id) // нет return! промис потерян
})
.then(orders => console.log(orders)) // orders === undefined
// Правильно
fetchUser(id)
.then(user => fetchOrders(user.id)) // стрелочная функция с implicit return
.then(orders => console.log(orders))Ошибка 3: нет .catch в конце
// Неправильно — ошибка «проглатывается», ничего не видно
fetchUser(id)
.then(user => renderUser(user))
// Если fetchUser упадёт — UnhandledPromiseRejection, тихий баг
// Правильно
fetchUser(id)
.then(user => renderUser(user))
.catch(err => console.error('Ошибка:', err.message))Конвейер обработки заказа: загрузка → проверка → обогащение данных → форматирование
function simulateFetch(data, delay = 150, shouldFail = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail) reject(new Error('Сетевая ошибка'))
else resolve(data)
}, delay)
})
}
const users = [
{ id: 1, name: 'Иван Петров', active: true },
{ id: 2, name: 'Анна Сидорова', active: false },
{ id: 3, name: 'Олег Козлов', active: true },
]
const orders = [
{ id: 101, userId: 1, total: 2500, items: 3 },
{ id: 102, userId: 3, total: 890, items: 1 },
]
// Цепочка: загрузить пользователей → оставить активных → обогатить заказами → форматировать
simulateFetch(users)
.then(list => list.filter(u => u.active)) // только активные
.then(active => {
const ids = active.map(u => u.id)
const userOrders = orders.filter(o => ids.includes(o.userId))
return simulateFetch(userOrders) // загружаем заказы
})
.then(activeOrders => activeOrders.map(order => ({
...order,
summary: `Заказ #${order.id}: ${order.items} товара на ${order.total}₽`
})))
.then(result => {
console.log('Активные заказы:')
result.forEach(o => console.log(' -', o.summary))
// - Заказ #101: 3 товара на 2500₽
// - Заказ #102: 1 товара на 890₽
})
.catch(err => console.error('Ошибка в конвейере:', err.message))
.finally(() => console.log('Загрузка завершена'))
// Пример восстановления после ошибки (fallback)
const cache = [{ id: 1, name: 'Иван (из кеша)', active: true }]
simulateFetch(null, 100, true) // API упал
.catch(() => simulateFetch(cache, 50)) // берём из кеша
.then(data => console.log('Данные:', data[0].name))
// 'Данные: Иван (из кеша)'
.catch(err => console.error('Даже кеш недоступен:', err.message))Напиши конвейер обработки данных каталога товаров интернет-магазина: загрузи список через simulateFetch, добавь каждому товару поле priceWithTax (цена + 20% НДС, округлённая до 2 знаков), отфильтруй только товары в наличии (inStock: true), отсортируй по цене по возрастанию, выведи результат. Обработай ошибки и добавь .finally.
priceWithTax: parseFloat((p.price * 1.2).toFixed(2)). Фильтрация: products.filter(p => p.inStock). Сортировка: [...products].sort((a, b) => a.priceWithTax - b.priceWithTax). Catch: err => console.error("Ошибка:", err.message).