Промисы и async/await в JavaScript: короткое руководство
Как перестать бояться Promise и научиться писать асинхронный код через async/await. С чего начать, что нужно заранее и как поймать типичные ошибки новичка.

К концу этого руководства вы напишете функцию, которая запрашивает данные с сервера и ждёт ответа, не блокируя браузер, поймёте разницу между .then() и await и научитесь ловить ошибки, которые иначе молча пропадают. На всё уйдёт около 20 минут, если под рукой есть браузер или Node.js.
Что нужно иметь до начала
Ничего устанавливать не обязательно — весь код запускается в консоли браузера (F12 → вкладка Console) или в Node.js. Проверьте только версию среды:
- Браузер — любой современный (Chrome, Firefox, Safari последних лет).
async/awaitподдерживается с 2017 года. - Node.js — версия 8 и выше. Проверить: команда
node -vв терминале. Все примеры ниже проверены на Node.js 20.x. - Базовое знакомство с функциями и
console.log. Про колбэки знать заранее не нужно.
Шаг 1. Что такое Promise на пальцах
Промис — это объект-обещание: «значение будет готово позже». У него три состояния: ожидание (pending), выполнено (fulfilled) и отклонено (rejected). Пока значение не пришло, код не стоит на месте — браузер продолжает работать.
Создадим промис вручную, чтобы увидеть его руками. Скопируйте в консоль:
// Промис, который выполнится через 1 секунду
const delay = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Готово через секунду");
}, 1000);
});
delay.then((результат) => {
console.log(результат); // выведет строку через 1 секунду
});
Проверка: в консоли через секунду появится строка «Готово через секунду». Если появилась сразу — вы забыли setTimeout или запустили не тот фрагмент.
Шаг 2. Настоящий запрос через fetch
В реальной жизни промисы возвращают встроенные функции. Самая частая — fetch, она запрашивает данные по сети и возвращает промис. Возьмём публичный тестовый API:
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((ответ) => ответ.json()) // тело ответа тоже приходит промисом
.then((данные) => {
console.log(данные.title);
});
Проверка: в консоли появится строка с текстом задачи (что-то вроде «delectus aut autem»). Обратите внимание на цепочку из двух .then(): fetch отдаёт ответ, а ответ.json() — ещё один промис с уже разобранными данными.
Шаг 3. Переписываем на async/await
Цепочки из .then() читаются тяжело, особенно когда их пять. async/await — это тот же промис, но записанный так, будто код выполняется сверху вниз по строкам.
Слово async перед функцией означает «внутри будут ожидания». Слово await перед промисом означает «дождись результата, прежде чем идти дальше». Тот же запрос:
async function загрузитьЗадачу() {
const ответ = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const данные = await ответ.json();
console.log(данные.title);
}
загрузитьЗадачу();
Проверка: результат тот же, что на шаге 2, но код читается линейно. await работает только внутри функции, помеченной async — это первое, обо что спотыкаются новички.
await не «останавливает браузер». Он ставит на паузу только текущую async-функцию, а остальной код и интерфейс продолжают работать. Это ключевое отличие от блокирующего ожидания.
Шаг 4. Ловим ошибки
Сеть падает, сервер возвращает 500, интернет отваливается. Промис в таком случае переходит в состояние rejected, и если ошибку не поймать, она «утечёт» без следа. В async/await для этого используется обычный try/catch:
async function загрузитьБезопасно() {
try {
const ответ = await fetch("https://jsonplaceholder.typicode.com/todos/1");
if (!ответ.ok) {
throw new Error("Сервер вернул статус " + ответ.status);
}
const данные = await ответ.json();
console.log(данные.title);
} catch (ошибка) {
console.error("Не удалось загрузить:", ошибка.message);
}
}
загрузитьБезопасно();
Проверка: при рабочем интернете увидите текст задачи. Отключите сеть или подставьте битый URL — в консоли появится строка «Не удалось загрузить: …» вместо необработанной ошибки. Важная деталь: fetch не считает ответ с кодом 404 или 500 ошибкой сам по себе, поэтому проверка ответ.ok нужна вручную.
Шаг 5. Несколько запросов сразу
Если нужно загрузить три ресурса, не заставляйте их ждать друг друга по очереди. await подряд — это последовательность, три запроса займут сумму их времени. Чтобы запустить их одновременно, используйте Promise.all:
async function загрузитьВсе() {
const [первая, вторая, третья] = await Promise.all([
fetch("https://jsonplaceholder.typicode.com/todos/1").then(r => r.json()),
fetch("https://jsonplaceholder.typicode.com/todos/2").then(r => r.json()),
fetch("https://jsonplaceholder.typicode.com/todos/3").then(r => r.json())
]);
console.log(первая.title, вторая.title, третья.title);
}
загрузитьВсе();
Проверка: в консоли появятся три заголовка. Разница со «стеной из await» видна на медленной сети: Promise.all ждёт самый долгий из трёх, а не их сумму. Оговорка: если хоть один промис в Promise.all упадёт, упадёт весь вызов — для устойчивости к отдельным сбоям есть Promise.allSettled, но это уже отдельная тема.
Если не получилось
Пять ошибок, которые видят почти все в первую неделю:
- SyntaxError: await is only valid in async functions — вы поставили
awaitвне функции сasync. Оберните код в async-функцию или, в Node.js новых версий, используйте await на верхнем уровне модуля (файл с расширением.mjsили"type": "module"в package.json). - В консоли Promise { <pending> } вместо данных — вы вывели сам промис, а не результат. Забыли
awaitили.then(). - Uncaught (in promise) TypeError — промис упал, а
try/catchили.catch()нет. Ошибка не исчезает, просто её никто не ловит. - Данные приходят undefined — частая причина: забыли
await ответ.json()и обращаетесь к полям неразобранного ответа. - Функция «ничего не возвращает» — async-функция всегда возвращает промис. Чтобы получить её результат снаружи, тоже нужен
awaitили.then().
.then или await — когда что
Оба способа делают одно и то же, выбор чаще про читаемость:
| Ситуация | Удобнее |
|---|---|
| Линейная последовательность шагов | async/await |
| Одна короткая операция без ветвлений | .then() |
| Обработка ошибок в нескольких местах | async/await + try/catch |
| Быстрая обработка результата на лету | .then() |
Что делать дальше
Вы умеете создавать промисы, ждать их через await, ловить ошибки и запускать запросы параллельно. Следующие шаги: разберитесь с Promise.allSettled и Promise.race, изучите отмену запросов через AbortController (актуально для fetch, когда пользователь ушёл со страницы) и почитайте про микрозадачи — порядок, в котором движок выполняет промисы относительно обычного кода. Это объяснит неочевидные случаи, когда console.log печатается «не в том порядке».
Инфографика для маркетплейса: Три главных преимущества товара короткими фразами, выноски в безопасной зоне превью, точный размер площадки.
Спросить за 60 ₽Источники: MDN — Using Promises, MDN — async function
Частые вопросы
Чем промис отличается от колбэка?
А если у меня старый браузер или Node.js версии 6?
Почему код после await не выполняется?
Можно ли использовать await на верхнем уровне файла, без функции?
fetch не бросает ошибку при статусе 404 — это баг?
Что выбрать в проекте — Promise.all или последовательные await?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.