В предыдущей главе мы написали fetch через .then(). Это работает, но если запросов становится больше, код быстро превращается в лесенку из вложенных колбэков. Сравните — и почувствуйте разницу.
Старый синтаксис: .then().catch()
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
.then(post => {
return fetch(`https://jsonplaceholder.typicode.com/users/${post.userId}`);
})
.then(response => response.json())
.then(user => {
console.log(`Пост "${post.title}" написал ${user.name}`);
})
.catch(error => console.error(error));
Здесь два последовательных запроса: сначала тянем пост, потом по его userId тянем автора. Уже на двух запросах появляется проблема: переменная post в последнем .then() недоступна — её область видимости закончилась двумя ступенями выше. Приходится либо выносить во внешний скоуп, либо вкладывать .then() друг в друга — собственно, та самая лесенка.
Новый синтаксис: async/await
Тот же код через async/await:
async function loadPostWithAuthor() {
try {
const postResponse = await fetch('https://jsonplaceholder.typicode.com/posts/1');
if (!postResponse.ok) throw new Error(`HTTP ${postResponse.status}`);
const post = await postResponse.json();
const userResponse = await fetch(`https://jsonplaceholder.typicode.com/users/${post.userId}`);
if (!userResponse.ok) throw new Error(`HTTP ${userResponse.status}`);
const user = await userResponse.json();
console.log(`Пост "${post.title}" написал ${user.name}`);
} catch (error) {
console.error(error);
}
}
loadPostWithAuthor();
Те же действия, но код читается сверху вниз, как обычный синхронный. Переменная post доступна до конца функции. Ошибки ловятся через привычный try/catch.
Правила игры
Чтобы это работало, нужно знать две вещи.
1. await можно ставить только внутри async-функции. Если попробовать использовать await в обычной функции — синтаксическая ошибка. В современных модулях ES2022+ есть top-level await (на уровне модуля без обёртки функцией), но в скриптах в браузере чаще всего нужна обёртка.
// Так нельзя — будет SyntaxError:
function bad() {
const r = await fetch('/'); // ошибка
}
// Так правильно:
async function good() {
const r = await fetch('/');
}
2. async-функция всегда возвращает Promise. Даже если внутри написать return 42, наружу прилетит Promise<42>. Это значит, что вызов async-функции, к которой не приставили .then() или await, — запускает её, но результат не дождётся.
Ошибки через try/catch
Главное преимущество async/await для новичка — знакомый try/catch. В нём ловятся:
- сетевые ошибки от fetch (нет интернета, DNS не разрешился);
- любые throw, которые мы делаем сами после проверки response.ok;
- ошибки парсинга в response.json() — например, если тело не JSON.
То же самое с .catch() делается, но синтаксис громоздче. Подробный разбор — в модуле 6.
Параллельные запросы: Promise.all
Один нюанс. Если запросы не зависят друг от друга — ставить их последовательно через await неэффективно. Каждый ждёт предыдущий, общее время — сумма всех.
// Долго: запросы идут один за другим
const a = await fetch('/api/a').then(r => r.json());
const b = await fetch('/api/b').then(r => r.json());
const c = await fetch('/api/c').then(r => r.json());
Если друг от друга они не зависят — запускаем параллельно:
// Быстро: запросы летят одновременно
const [a, b, c] = await Promise.all([
fetch('/api/a').then(r => r.json()),
fetch('/api/b').then(r => r.json()),
fetch('/api/c').then(r => r.json()),
]);
Promise.all принимает массив Promise-ов и возвращает один общий Promise, который разрешится, когда разрешатся все. Если хотя бы один упадёт — общий тоже упадёт. Если ошибки нужно ловить по отдельности, есть Promise.allSettled — он дождётся всех, и в результате каждого будет либо {status: 'fulfilled', value}, либо {status: 'rejected', reason}.
Сравним два стиля бок о бок
Что использовать в проекте
В современных кодовых базах async/await — стандарт. .then-цепочки встречаются в местах, где нужны короткие однострочники, или в утилитарном коде, который не выгодно оборачивать в async-функцию. Знать надо обе формы: при чтении чужого кода обе встречаются.
В этом курсе дальше используем async/await как основной стиль.