Promise
Promise
Технический фундамент Promise-цепочек
Promise задает строгий контракт распространения результата:
- каждый
thenполучает результат предыдущего шага; - брошенная ошибка в любом
thenпереводит цепочку вrejected; - ближайший
catchперехватывает ошибку всей цепочки; finallyне меняет значение цепочки без явногоthrow/return Promise.reject(...).
Это делает поток данных и ошибок линейным и управляемым даже при сложной асинхронной логике.
Зачем появился Promise
Callback решает много задач, но при сложных цепочках быстро растет вложенность и ухудшается обработка ошибок. Promise появился как более структурированный способ работать с асинхронным результатом.
Ключевой момент: Promise это объект, который представляет будущий результат операции: успех или ошибку.
Проверь себя: почему Promise удобнее callback при нескольких последовательных шагах?
Состояния Promise
У Promise есть три состояния:
pending- операция еще выполняется;fulfilled- успешно завершилась;rejected- завершилась ошибкой.
После перехода в fulfilled или rejected состояние уже не меняется.
const promise = new Promise((resolve, reject) => {
const isOk = true;
if (isOk) resolve('Готово');
else reject(new Error('Сбой'));
});
promise.then((value) => console.log(value)).catch((error) => console.log(error.message));
Смотри, что важно: resolve и reject это не "return", а сигналы завершения асинхронной операции.
Смотри, что важно: функция-исполнитель (executor) внутри new Promise((resolve, reject) => { ... }) запускается сразу, синхронно. Если внутри executor случится throw, Promise станет rejected.
new Promise(() => {
throw new Error('boom');
}).catch((error) => console.log('rejected:', error.message));
then, catch, finally
thenобрабатывает успешный результат;catchобрабатывает ошибку;finallyвыполняется в любом случае.
fetchData()
.then((data) => transformData(data))
.then((result) => console.log(result))
.catch((error) => console.error('Ошибка:', error.message))
.finally(() => console.log('Запрос завершен'));
Здесь часто путаются: если в одном then произошла ошибка, управление перескочит в ближайший catch.
Смотри, что важно: catch не только "ловит" ошибку, но и может восстановить цепочку, если вернуть значение.
Promise.reject(new Error('fail'))
.catch(() => 'fallback')
.then((value) => console.log(value)); // fallback
Смотри, что важно: finally удобно использовать для cleanup (например, убрать loading). Он не получает значение/ошибку и не должен "менять" результат цепочки. Но если из finally сделать throw, цепочка станет rejected.
Проверь себя: почему один общий catch в конце цепочки часто удобнее нескольких локальных?
Возврат значения из then
Если ты возвращаешь значение из then, оно попадет в следующий then.
Promise.resolve(5)
.then((num) => num * 2)
.then((num) => `Результат: ${num}`)
.then((text) => console.log(text));
Если вернуть новый Promise, цепочка подождет его завершения.
Это делает последовательные асинхронные шаги читаемыми без глубокой вложенности.
Promise.all и параллельные операции
Когда несколько независимых запросов можно выполнить одновременно, используют Promise.all.
Promise.all([loadUser(), loadSettings(), loadNotifications()])
.then(([user, settings, notifications]) => {
console.log(user, settings, notifications.length);
})
.catch((error) => {
console.error('Один из запросов упал:', error.message);
});
Ключевой момент: Promise.all завершится ошибкой, если хотя бы один Promise rejected.
Смотри, что важно:
Promise.all"падает быстро" при первомrejected, но остальные операции не отменяются автоматически.- Если нужно дождаться результатов всех операций (даже упавших), используй
Promise.allSettled. - Если важно "кто завершился первым" (успех или ошибка), используй
Promise.race. - Если нужен первый успешный результат, используй
Promise.any(а если все упали, будет ошибка-агрегат).
Микро-сценарии применения
- Экран профиля: параллельно загрузить данные пользователя и настройки.
- Каталог товаров: получить список, затем подгрузить метаданные и объединить.
В обоих случаях Promise помогает выстроить понятный поток данных.
Типичные ошибки новичков
- Забывать
returnвнутриthen, из-за чего цепочка теряет данные. - Писать
catchслишком рано и "глушить" ошибку. - Смешивать callback и
Promiseбез четкого контракта. - Считать, что
Promiseвыполняется синхронно.
let value = 0;
Promise.resolve(10).then((v) => {
value = v;
});
console.log(value); // 0, потому что then выполнится позже
Анти-провал: все, что зависит от результата Promise, размещай в then-цепочке или в async/await (следующий урок).
Что будет, если изменить входные данные
Если в new Promise(...) поменять isOk на false, код пойдет в catch, а then-обработчик успеха не выполнится. Это и есть контракт: либо успех, либо ошибка.
Проверь себя: выполнится ли finally, если был rejected? Почему?
Дополнительный пример: ошибка в середине цепочки и единый catch.
Promise.resolve('start')
.then(() => {
throw new Error('Ошибка на шаге обработки');
})
.catch((error) => console.log('Поймали:', error.message));
Краткий итог
Promiseпредставляет будущий результат асинхронной операции.- Основные состояния:
pending,fulfilled,rejected. - Цепочки
thenделают последовательную обработку читаемой. catchцентрализует обработку ошибок,finallyвыполняется всегда.Promise.allполезен для параллельных задач, но падает при ошибке любого участника.