Как работают промисы (Promise) и чем отличаются от async/await?

Promise — это объект, представляющий результат асинхронной операции, которая может завершиться в будущем с успешным результатом (resolved) или с ошибкой (rejected). Он позволяет организовать асинхронный код без вложенных коллбэков.

1. Состояния промиса

У промиса есть три состояния:

  1. pending — начальное состояние, операция ещё не завершена.

  2. fulfilled — операция завершилась успешно, есть результат.

  3. 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);
}
}