Промисы и async/await в JavaScript: понятное руководство с примерами и лучшими практиками
Промисы и async/await в JavaScript: понятное руководство с примерами и лучшими практиками
Запрос: «промисы в JavaScript для начинающих», «async await примеры». Это руководство поможет быстро освоить асинхронный код, избежать частых ошибок и писать надёжно и читаемо.
Что такое промис простыми словами
Promise — это объект, который представляет результат асинхронной операции: «ожидание» (pending), «успех» (fulfilled) или «ошибка» (rejected). Работать с ним можно через then/catch/finally или с помощью синтаксиса async/await.
const p = new Promise((resolve, reject) => {
setTimeout(() => resolve(42), 500);
});
p
.then(value => value * 2)
.then(double => console.log('Результат:', double))
.catch(err => console.error('Ошибка:', err))
.finally(() => console.log('Готово'));
async/await: тот же промис, но читабельнее
async-функция возвращает промис. Оператор await «ждёт» промис и возвращает его значение или бросает исключение при отклонении.
async function run() {
try {
const value = await p; // 42 через ~500 мс
console.log('Результат:', value * 2);
} catch (e) {
console.error('Ошибка:', e);
} finally {
console.log('Готово');
}
}
run();
Последовательное vs параллельное выполнение
Частая ошибка — выполнять независимые задачи последовательно вместо параллельного запуска. Сравним:
// Эмулятор загрузки
const load = (id) => new Promise(res => setTimeout(() => res(`item-${id}`), 300));
const ids = [1, 2, 3, 4];
// Плохо: последовательно (дольше)
async function sequential() {
const out = [];
for (const id of ids) {
out.push(await load(id));
}
return out;
}
// Хорошо: параллельно
async function parallel() {
return Promise.all(ids.map(load));
}
sequential().then(r => console.timeEnd('seq'));
console.time('seq');
parallel().then(r => console.timeEnd('par'));
console.time('par');
Итог: используйте Promise.all для независимых задач. Последовательность нужна, когда следующий шаг зависит от результата предыдущего или вы ограничиваете конкуренцию.
Полезные комбинаторы: all, allSettled, race, any
// 1) Ждём всех, падаем если хоть один упадёт
await Promise.all([taskA(), taskB(), taskC()]);
// 2) Ждём всех, всегда получаем статусы
const results = await Promise.allSettled([taskA(), taskB()]);
// results: [{ status: 'fulfilled', value }, { status: 'rejected', reason }]
// 3) Кто быстрее — тот и результат (включая ошибку)
const first = await Promise.race([slowTask(), fastTask()]);
// 4) Ждём первый успешный, если все упали — AggregateError
try {
const ok = await Promise.any([mayFail1(), mayFail2()]);
} catch (e) {
// e instanceof AggregateError
}
Таймауты и отмена: практичные шаблоны
Таймаут через Promise.race:
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout ${ms}ms`)), ms)
);
return Promise.race([promise, timeout]);
}
// Пример:
await withTimeout(load(1), 500);
Отмена через AbortController (поддерживается fetch и многими API):
const controller = new AbortController();
const { signal } = controller;
setTimeout(() => controller.abort(), 2000); // отменим через 2с
try {
const res = await fetch('https://example.com/data', { signal });
const data = await res.json();
console.log(data);
} catch (e) {
if (e.name === 'AbortError') {
console.warn('Запрос отменён');
} else {
console.error('Ошибка запроса', e);
}
}
Типичные ошибки и как их избежать
- Забыли вернуть промис в .then — цепочка «теряется». Решение: всегда return внутри then, если используете цепочки.
- Смешивание then и await в одном фрагменте — ухудшает читаемость. Выберите один стиль на участок кода.
- async в forEach. forEach не ждёт промисы — используйте for...of или map + Promise.all.
- Нет обработки ошибок — получите unhandledrejection. Всегда ловите ошибки try/catch или .catch.
Пример с forEach: как правильно
// Плохо: не ждёт завершения
ids.forEach(async (id) => {
await load(id);
});
// Хорошо 1: последовательная обработка
for (const id of ids) {
await load(id);
}
// Хорошо 2: параллельно и ждём всех
await Promise.all(ids.map(id => load(id)));
Преобразование колбэков в промисы (promisify)
Иногда встречаются функции формата callback(err, result). Их удобно оборачивать в промисы.
function toPromise(fn) {
return (...args) => new Promise((resolve, reject) => {
fn(...args, (err, result) => (err ? reject(err) : resolve(result)));
});
}
// Пример с псевдо-колбэком
function legacyMultiply(x, cb) {
setTimeout(() => cb(null, x * 2), 200);
}
const multiplyAsync = toPromise(legacyMultiply);
const value = await multiplyAsync(5); // 10
Ограничение конкуренции (concurrency limit)
Не всегда можно запускать сотни запросов параллельно — лимиты и ресурсы не бесконечны. Используйте паттерн mapLimit.
async function mapLimit(items, limit, iteratee) {
const results = [];
const executing = new Set();
for (const item of items) {
const p = Promise.resolve()
.then(() => iteratee(item))
.then((res) => {
results.push(res);
executing.delete(p);
});
executing.add(p);
if (executing.size >= limit) {
await Promise.race(executing);
}
}
await Promise.all(executing);
return results;
}
// Пример использования
const out = await mapLimit(ids, 2, load); // максимум 2 одновременные задачи
Чеклист лучших практик
- Независимые задачи запускайте через Promise.all — это быстрее.
- Добавляйте таймауты к сетевым операциям и умейте отменять (AbortController).
- Стандартизируйте стиль: либо then-цепочки, либо async/await на участке кода.
- Ловите ошибки: try/catch и .catch; логируйте и показывайте понятные сообщения.
- Следите за конкуренцией: используйте лимиты и очереди для тяжёлых задач.
- Не используйте async в forEach/Map/Filter без понимания поведения — применяйте for...of или Promise.all.
Куда двигаться дальше
Закрепить тему можно на реальных мини-проектах: параллельная загрузка ресурсов с лимитом, отмена долгих запросов, обработка частично успешных результатов через allSettled. Если хотите системно пройти основы и быстро перейти к уверенной практике, загляните сюда: Освоить асинхронность и не только на курсе «JavaScript с Нуля до Гуру 2.0» — стартуйте сегодня.
Промисы и async/await — фундамент современного JavaScript. Освоив их, вы пишете менее «шумный» и более надёжный код, контролируете конкурентность и уверенно обрабатываете ошибки.
-
Создано 12.11.2025 17:01:09
-
Михаил Русаков

Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.