Как работают промисы (Promise) и чем отличаются от async/await?
Promise — это объект, представляющий результат асинхронной операции, которая может завершиться в будущем с успешным результатом (resolved) или с ошибкой (rejected). Он позволяет организовать асинхронный код без вложенных коллбэков.
1. Состояния промиса
У промиса есть три состояния:
-
pending — начальное состояние, операция ещё не завершена.
-
fulfilled — операция завершилась успешно, есть результат.
-
rejected — операция завершилась с ошибкой.
После перехода из pending в одно из двух других состояний (fulfilled или rejected), промис считается завершённым и его состояние становится неизменным.
2. Создание промиса
const promise = new Promise((resolve, reject) => {
// асинхронный код
if (успех) {
resolve(result);
} else {
reject(error);
}
});
Пример:
const fetchData = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Данные получены');
}, 1000);
});
3. Обработка результатов
Методы:
-
.then(onFulfilled) — вызывается при успешном завершении.
-
.catch(onRejected) — вызывается при ошибке.
-
.finally(onFinally) — вызывается вне зависимости от результата.
fetchData
.then(data => {
console.log(data); // 'Данные получены'
})
.catch(error => {
console.error(error);
})
.finally(() => {
console.log('Операция завершена');
});
4. Цепочка промисов
Можно выстраивать последовательность из .then():
doStep1()
.then(result1 => doStep2(result1))
.then(result2 => doStep3(result2))
.then(finalResult => console.log(finalResult))
.catch(error => console.error(error));
Каждый .then получает результат предыдущего и возвращает новый промис. Это даёт гибкость при построении асинхронных цепочек.
5. Обработка ошибок
Любая ошибка (в том числе исключение в .then()) будет перехвачена ближайшим .catch():
fetchData
.then(() => {
throw new Error('Ошибка в обработке');
})
.catch(err => console.error(err.message)); // 'Ошибка в обработке'
6. Promise.all, Promise.race, Promise.allSettled, Promise.any
-
Promise.all([p1, p2, ...]) — ждет выполнения всех. Если один завершится с ошибкой — весь промис будет rejected.
-
Promise.race([p1, p2, ...]) — завершится тем промисом, который первым выполнится (успешно или с ошибкой).
-
Promise.allSettled([p1, p2, ...]) — ждет выполнения всех и возвращает массив с результатами (не прерывается при ошибке).
-
Promise.any([p1, p2, ...]) — ждет первый успешный промис, игнорирует ошибки, пока не исчерпает все.
Пример Promise.all:
Promise.all(\[fetch1(), fetch2()\])
.then((\[res1, res2\]) => {
// оба завершились успешно
})
.catch(err => {
// хотя бы один упал
});
7. async / await
async / await — синтаксический сахар над промисами. Позволяет писать асинхронный код, как синхронный, но под капотом всё равно используется Promise.
async function getData() {
const data = await fetchData();
console.log(data);
}
-
Ключевое слово async делает функцию асинхронной и возвращающей Promise.
-
await приостанавливает выполнение функции до завершения промиса.
8. async возвращает Promise
async function f() {
return 42;
}
f().then(res => console.log(res)); // 42
Даже если функция явно не возвращает промис, она оборачивается в Promise.resolve().
9. await можно использовать только внутри async
await fetch(); // SyntaxError вне async-функции
async function main() {
await fetch(); // работает
}
10. Обработка ошибок в async/await
Через try/catch:
async function getUser() {
try {
const user = await fetchUser();
console.log(user);
} catch (err) {
console.error('Ошибка:', err);
}
}
Это делает обработку ошибок более естественной по сравнению с .catch().
11. Сравнение промисов и async/await
Особенность | Promise | async/await |
---|---|---|
Управление цепочкой | Через .then() и .catch() | Через await и try/catch |
--- | --- | --- |
Синтаксис | Функциональный, вложенный | Линейный, похож на синхронный |
--- | --- | --- |
Ошибки | Ловятся через .catch() | Ловятся через try/catch |
--- | --- | --- |
Поведение | Явное управление цепочкой | Пауза выполнения до результата |
--- | --- | --- |
Уровень читаемости | Менее читаемый при вложенности | Более читаемый и простой |
--- | --- | --- |
12. Пример сравнения
Промисы:
function getUserData() {
return fetch('/user')
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(err => {
console.error(err);
});
}
async/await:
async function getUserData() {
try {
const response = await fetch('/user');
const data = await response.json();
console.log(data);
} catch (err) {
console.error(err);
}
}
13. Параллельные запросы с await и Promise.all
Если сделать несколько await подряд, они выполняются последовательно:
const a = await fetchA();
const b = await fetchB(); // начнётся только после A
Чтобы выполнить параллельно:
const \[a, b\] = await Promise.all(\[fetchA(), fetchB()\]);
14. Поведение при ошибке в await
Если await ждёт промис, который завершается с ошибкой — генерируется исключение:
async function f() {
await Promise.reject('Ошибка'); // выбросится ошибка
}
Если без try/catch, ошибка "вылетит" и промис из функции станет rejected.
15. await можно использовать с любыми thenable-объектами
await работает не только с Promise, но и с любыми объектами, у которых есть метод .then():
const thenable = {
then(resolve, reject) {
setTimeout(() => resolve('OK'), 1000);
}
};
async function f() {
const result = await thenable;
console.log(result); // OK
}
16. Пример с вложенной логикой
На промисах:
getUser()
.then(user => getPosts(user.id))
.then(posts => getComments(posts\[0\].id))
.then(comments => console.log(comments))
.catch(err => console.error(err));
На async/await:
async function fetchData() {
try {
const user = await getUser();
const posts = await getPosts(user.id);
const comments = await getComments(posts\[0\].id);
console.log(comments);
} catch (err) {
console.error(err);
}
}