Асинхронность в JavaScript
промисы, await и четыре места, где ИИ ошибается
Что это
Асинхронность в JavaScript устроена так же, как в Python (статья 2.10):
async, await, ожидание вместо простоя. Но есть своя терминология и свои
ловушки, и все четыре ниже ИИ воспроизводит регулярно.
Здесь же — прямая параллель с главой 6: Promise.all и Promise.allSettled
соотносятся ровно так же, как gather и gather(return_exceptions=True).
Как выглядит в коде
const fetchCity = (city, ms = 100) =>
new Promise((resolve, reject) =>
setTimeout(() => city === "Bergen"
? reject(new Error("сервер вернул 500"))
: resolve(`${city}: +4`), ms));Промис (promise) — обещание результата, который появится позже. У него
три состояния: ожидает, выполнен, отклонён. resolve — успех, reject —
ошибка.
Писать new Promise вручную почти никогда не нужно: промисы возвращают
fetch и библиотеки. Но знать, что это такое, надо — слово встречается
постоянно.
1. Promise.all роняет всё целиком
await Promise.all([fetchCity("Oslo"), fetchCity("Bergen"), fetchCity("Tromsø")]);упало целиком: сервер вернул 500Запросы к Осло и Тромсё прошли успешно — их результаты потеряны. Ровно то же,
что asyncio.gather без защиты в Python.
2. Promise.allSettled сохраняет успехи
const r = await Promise.allSettled([...]);
for (const x of r)
console.log(x.status === "fulfilled" ? `ок: ${x.value}` : `ошибка: ${x.reason.message}`);ок: Oslo: +4
ошибка: сервер вернул 500
ок: Tromsø: +4Каждый элемент — объект со status. У успешного есть value, у неудачного —
reason. Проверять status обязательно: иначе ошибка уедет дальше как
обычное значение.
| Нужно | Что брать |
|---|---|
| Все должны получиться, иначе смысла нет | Promise.all |
| Частичный результат лучше, чем ничего | Promise.allSettled |
| Достаточно первого успешного ответа | Promise.any |
| Важен первый ответ любой, даже ошибка (например, гонка с таймаутом) | Promise.race |
3. forEach не ждёт
Самая коварная из четырёх:
const done = [];
["Oslo", "Tromsø"].forEach(async c => { done.push(await fetchCity(c)); });
console.log("сразу после forEach:", done);сразу после forEach: []
через 300 мс: [ 'Oslo: +4', 'Tromsø: +4' ]Массив пуст. forEach не умеет ждать async-функции: он запускает их все и
идёт дальше, не дожидаясь. Данные появятся — но позже, когда код, который их
ждал, уже отработал.
Правильно — обычный цикл:
for (const c of ["Oslo", "Tromsø"]) ok.push(await fetchCity(c));после цикла: [ 'Oslo: +4', 'Tromsø: +4' ]Или Promise.all с map, если запросы независимы и хочется параллельно.
4. Забытый await
без await: Promise { <pending> }
с await: Oslo: +4Вместо значения — объект промиса. В Python это <coroutine object>, здесь
Promise { <pending> } — тот же симптом, другое слово.
Как сказать ИИ
Эти три запроса независимы. Нужен частичный результат:
если один сервер не ответил, остальные должны дойти.
Используй Promise.allSettled и разбери status.В этом коде forEach с async внутри. Перепиши так,
чтобы результаты действительно дожидались: for...of,
если запросы зависят друг от друга, или Promise.all с map,
если нет.Добавь таймаут 5 секунд на каждый запрос через AbortSignal.timeout.
При таймауте — предупреждение в консоль и пропуск, а не падение.На что смотреть
Promise.all там, где нужен частичный результат. Одна ошибка теряет всё.
forEach(async ...). Не ждёт. Всегда ошибка, если результат нужен.
Нет .catch и нет try. Отклонённый промис, который никто не поймал, даёт
UnhandledPromiseRejection — в Node это падение процесса.
Последовательные await в цикле там, где можно параллельно. Не ошибка,
но втрое медленнее на трёх запросах (статья 2.10).
Нет таймаута. Запрос может висеть вечно (статья 6.4). В браузере и Node
для этого есть AbortSignal.timeout(ms).
.then() вперемешку с await. Работает, читается плохо. В новом коде —
await.
Шпаргалка
Промис — обещание результата;
resolveуспех,rejectошибка.Promise.allроняет всё при первой ошибке,allSettledсохраняет успехи.У
allSettledпроверяйstatus:fulfilled→value,rejected→reason.forEach(async ...)не ждёт. Нуженfor...ofилиPromise.all(map).Promise { <pending> }в выводе — забытawait.Непойманный отклонённый промис роняет процесс в Node.