Промисы и async/await в JavaScript: пошаговая инструкция
За 30–40 минут вы напишете первый асинхронный код: получите данные с сервера, обработаете ошибку и запустите несколько запросов параллельно. Всё на чистом JavaScript, без библиотек.

К концу этой инструкции у вас будет рабочий скрипт, который делает запрос к публичному API, разбирает ответ, корректно ловит ошибку сети и запускает несколько запросов одновременно. Займёт 30–40 минут. Разбираться в устройстве event loop не нужно — сейчас важно получить результат и понять, в каком порядке пишутся строки.
Всё работает в браузере (любой актуальный Chrome, Firefox, Safari) и в Node.js 18 и новее. В Node 18+ функция fetch встроена, отдельный пакет ставить не нужно.
Что нужно до начала
- Node.js версии 18 или новее — проверьте командой
node --version. Либо просто открытая вкладка браузера и его консоль (F12 → Console). - Любой редактор кода. Подойдёт даже Блокнот, но с подсветкой синтаксиса удобнее.
- Доступ в интернет — примеры обращаются к тестовому API
jsonplaceholder.typicode.com, который отдаёт фейковые данные и не требует ключа.
Файлы с расширением .js в Node запускаются командой node имя_файла.js. В браузере код можно вставлять прямо в консоль и жать Enter.
Шаг 1. Понять, что возвращает промис
Промис — это объект, который обещает дать результат позже: когда придёт ответ сервера, когда прочитается файл, когда истечёт таймер. Пока результата нет, промис находится в состоянии ожидания. Потом он либо выполняется с данными, либо отклоняется с ошибкой.
Функция fetch возвращает именно промис. Проверьте это:
// Node.js 18+ или консоль браузера
const obещание = fetch('https://jsonplaceholder.typicode.com/todos/1');
console.log(obещание); // Promise { <pending> } — результата ещё нет
В консоли вы увидите Promise { <pending> } или похожую запись. Это нормально: данные ещё в пути. Достать их синхронно, как обычную переменную, нельзя — нужно дождаться.
Проверка шага: в консоли выведен объект Promise, а не текст ответа сервера.
Шаг 2. Дождаться результата через then
Исторически первый способ получить данные из промиса — метод .then(). В него передаётся функция, которую движок вызовет, когда результат будет готов.
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(function (ответ) {
// ответ — это ещё не данные, а объект Response
return ответ.json(); // .json() тоже возвращает промис
})
.then(function (данные) {
console.log(данные); // { userId: 1, id: 1, title: '...', completed: false }
});
Обратите внимание: fetch отдаёт объект ответа, а сами данные достаются вторым вызовом — .json(). Поэтому цепочка из двух .then().
Проверка шага: в консоли появился объект с полями userId, id, title, completed.
Шаг 3. Переписать на async/await
Цепочки .then() читаются тяжело, когда шагов много. Ключевые слова async и await позволяют писать асинхронный код так, будто он обычный последовательный. Правило: await работает только внутри функции, помеченной async.
// async перед функцией — теперь внутри можно ждать через await
async function получитьЗадачу() {
const ответ = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const данные = await ответ.json();
console.log(данные);
}
получитьЗадачу();
Строка с await приостанавливает функцию, пока промис не выполнится, и подставляет результат в переменную. Никаких вложенных функций — код читается сверху вниз.
Проверка шага: результат в консоли тот же, что на шаге 2, но кода меньше и он линейный.
Одна и та же логика работает и через .then, и через async/await. Это не разные механизмы, а разный синтаксис поверх одних и тех же промисов. Учить оба нужно: чужой код вы встретите в обоих видах.
Шаг 4. Поймать ошибку
Сеть падает, сервер отвечает 500, адрес опечатан. Без обработки ошибки такой промис отклонится, а вы получите в консоли необработанное отклонение. В async-функции ошибку ловят обычным try/catch.
async function получитьЗадачу(id) {
try {
const ответ = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
// fetch НЕ считает ошибкой ответы 404 и 500 — проверяем сами
if (!ответ.ok) {
throw new Error(`Сервер ответил статусом ${ответ.status}`);
}
const данные = await ответ.json();
console.log(данные);
} catch (ошибка) {
console.error('Не удалось получить данные:', ошибка.message);
}
}
получитьЗадачу(1); // успех
получитьЗадачу(999999); // покажет ошибку статуса
Важная деталь: fetch отклоняет промис только при сетевом сбое. Ответ с кодом 404 или 500 для него — успешный запрос, поэтому статус проверяют вручную через ответ.ok.
Проверка шага: первый вызов выводит данные, второй — строку «Не удалось получить данные».
Шаг 5. Запустить несколько запросов параллельно
Если написать три await подряд, запросы пойдут по очереди: второй стартует только после ответа на первый. Когда запросы не зависят друг от друга, это лишняя трата времени. Их запускают одновременно через Promise.all.
async function получитьНесколько() {
const адреса = [1, 2, 3].map(
(id) => fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
);
// ждём все три ответа сразу
const ответы = await Promise.all(адреса);
// разбираем каждый ответ в JSON — тоже параллельно
const данные = await Promise.all(ответы.map((r) => r.json()));
console.log(данные); // массив из трёх объектов
}
получитьНесколько();
Разница ощутима: три последовательных запроса по 200 мс дадут около 600 мс, а Promise.all — примерно 200 мс, потому что запросы идут одновременно. Точные числа зависят от сети и сервера.
Внимание: Promise.all отклоняется целиком, если упал хотя бы один промис. Если нужно получить результаты остальных даже при падении одного — есть Promise.allSettled. Это отдельная тема, здесь мы её не разворачиваем.
Проверка шага: в консоли — массив из трёх объектов задач.
Последовательно или параллельно: когда что
| Ситуация | Как писать |
|---|---|
| Второй запрос использует результат первого | Два await подряд |
| Запросы независимы, ждём все | await Promise.all([...]) |
| Ждём первый ответивший | await Promise.race([...]) |
| Нужны результаты даже при падении части | await Promise.allSettled([...]) |
Если не получилось
SyntaxError: await is only valid in async functions— вы поставилиawaitвне функции сasync. Оберните код вasync functionи вызовите её. В Node на верхнем уровне модуля.mjsawait работает и без обёртки, но в обычном.js-скрипте — нет.fetch is not defined— у вас Node старше 18. Обновите Node до 18+ либо запускайте код в браузере.- В консоли
Promise { <pending> }вместо данных — вы забылиawaitили.then()и выводите сам промис, а не его результат. UnhandledPromiseRejection— промис отклонился, аtry/catchили.catch()нет. Оберните вызов в обработку ошибки, как на шаге 4.- Данные приходят, но
ответ.json()падает — сервер вернул не JSON (например, HTML-страницу ошибки). Проверьтеответ.okиответ.statusдо вызова.json().
Что делать дальше
Вы умеете получать данные, ловить ошибки и запускать запросы параллельно — этого хватает для большинства задач в браузере и на сервере. Дальше стоит разобрать три вещи: Promise.allSettled для отказоустойчивых пачек запросов, отмену запроса через AbortController (когда пользователь ушёл со страницы) и то, почему await в цикле for делает запросы последовательными. Последнее — частая ошибка производительности, с которой сталкиваются, когда кода становится больше.
IT-архитектор / SQL-оптимизатор: Поиск N+1, оптимизация запросов, code review, поиск багов.
Спросить за 20 ₽Источники: MDN — Using promises, MDN — async function
Частые вопросы
А если у меня Node.js версии 16 или старше?
Можно ли использовать await на верхнем уровне файла, без функции?
Чем отличается .catch() от try/catch?
Почему fetch не считает ошибкой ответ 404?
Мои запросы в цикле идут медленно, по очереди — почему?
Async/await заменяет промисы полностью?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.