} Объясните поведение при частично неудачных задачах, когда Promise.reject пробрасывает ошибку, и сравните с Promise.allSettled; предложите подход для продолжения обработки и аккумулирования ошибок
Коротко — в чём проблема и чем отличается поведение:
Promise.reject(value) создаёт отклонённый (rejected) промис. Если вы используете await Promise.reject(...) внутри async-функции, то await бросит исключение (как будто вы написали throw), и выполнение этой async-функции перейдёт в ближайший catch (или промис вернётся в rejected-состояние).Promise.all([...]) завершится с rejected как только любой из входных промисов перейдёт в rejected. Это «короткое замыкание»: Promise.all возвращает rejected на первый отказ. (Важное уточнение: другие промисы при этом не «отменяются» — они всё ещё выполняются в фоне, но их результаты игнорируются.)Promise.allSettled([...]) ждёт, пока все промисы завершатся — независимо от того, fulfilled или rejected — и возвращает массив описаний статусов ({status: "fulfilled", value: ...} или {status: "rejected", reason: ...}). Это даёт вам полную картину: что прошло, что упало.
Примеры и паттерны для продолжения обработки и аккумулирования ошибок
1) Пример: поведение Promise.all (короткое замыкание)
const tasks = urls.map(fetchData); // fetchData возвращает промис try { const results = await Promise.all(tasks); // если хоть один fetchData отклонится — мы попадём в catch } catch (err) { console.error('Один из запросов упал:', err); // но другие запросы всё ещё могли завершиться (успех/провал), мы не видим их тут }
2) Использовать Promise.allSettled, собрать успешные результаты и ошибки
const results = await Promise.allSettled(tasks); const successes = results .filter(r => r.status === 'fulfilled') .map(r => r.value); const errors = results .filter(r => r.status === 'rejected') .map(r => r.reason); console.log('Успехи:', successes); console.log('Ошибки:', errors); // при необходимости можно бросить агрегированную ошибку if (errors.length) { throw new AggregateError(errors, `${errors.length} задач(-и) завершились с ошибкой`); }
3) Обёртка, чтобы каждое задание всегда «успешно» возвращало объект результата Иногда удобней явно вернуть объект вида {ok, value, error}:
4) Если нужно аккумулировать ошибки и при этом продолжать выполнять следующие задачи (параллельно или последовательно)
Параллельно: используйте allSettled/reflect, потом агрегируйте результаты.Последовательно с контролем остановки по порогу ошибок: const successes = []; const errors = []; for (const task of tasks) { try { const res = await task(); successes.push(res); } catch (err) { errors.push(err); if (errors.length > MAX_ERRORS) { throw new AggregateError(errors, 'Превышен лимит ошибок, прерывание'); } } }
5) AggregateError
Это встроённый класс ошибок (ES2021). Удобен для бросания «нескольких» ошибок сразу: if (errors.length) throw new AggregateError(errors, 'Некоторые задачи упали');
Дополнительные замечания
Promise.all не «отменяет» другие промисы. Если вам нужно уметь отменять (например, с AbortController), вы должны реализовать отмену явно.Если вы используете await внутри map без обработки ошибок, то получите незамеченные rejected-промисы. Всегда оборачивайте в try/catch, либо работайте через allSettled/reflect.Для контроля параллелизма и удобных опций (например continueOnError) можно использовать библиотеки (p-map, Bluebird и др.), но стандартные allSettled/AggregateError покрывают большинство сценариев.
Рекомендация
Если хотите дождаться всех задач и аккумулировать ошибки — используйте Promise.allSettled (или reflect). Затем обработайте списки success/error по своему сценарию (лог, повтор, агрегированная ошибка, метрика и т.д.). Если хотите прерывать при первой ошибке — используйте Promise.all (или let await внутри try/catch).
Коротко — в чём проблема и чем отличается поведение:
Promise.reject(value) создаёт отклонённый (rejected) промис. Если вы используете await Promise.reject(...) внутри async-функции, то await бросит исключение (как будто вы написали throw), и выполнение этой async-функции перейдёт в ближайший catch (или промис вернётся в rejected-состояние).Promise.all([...]) завершится с rejected как только любой из входных промисов перейдёт в rejected. Это «короткое замыкание»: Promise.all возвращает rejected на первый отказ. (Важное уточнение: другие промисы при этом не «отменяются» — они всё ещё выполняются в фоне, но их результаты игнорируются.)Promise.allSettled([...]) ждёт, пока все промисы завершатся — независимо от того, fulfilled или rejected — и возвращает массив описаний статусов ({status: "fulfilled", value: ...} или {status: "rejected", reason: ...}). Это даёт вам полную картину: что прошло, что упало.Примеры и паттерны для продолжения обработки и аккумулирования ошибок
1) Пример: поведение Promise.all (короткое замыкание)
const tasks = urls.map(fetchData); // fetchData возвращает промисtry {
const results = await Promise.all(tasks);
// если хоть один fetchData отклонится — мы попадём в catch
} catch (err) {
console.error('Один из запросов упал:', err);
// но другие запросы всё ещё могли завершиться (успех/провал), мы не видим их тут
}
2) Использовать Promise.allSettled, собрать успешные результаты и ошибки
const results = await Promise.allSettled(tasks);const successes = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
const errors = results
.filter(r => r.status === 'rejected')
.map(r => r.reason);
console.log('Успехи:', successes);
console.log('Ошибки:', errors);
// при необходимости можно бросить агрегированную ошибку
if (errors.length) {
throw new AggregateError(errors, `${errors.length} задач(-и) завершились с ошибкой`);
}
3) Обёртка, чтобы каждое задание всегда «успешно» возвращало объект результата
function reflect(p) {Иногда удобней явно вернуть объект вида {ok, value, error}:
return p
.then(value => ({ ok: true, value }))
.catch(reason => ({ ok: false, reason }));
}
const reflected = await Promise.all(tasks.map(t => reflect(t)));
const successes = reflected.filter(x => x.ok).map(x => x.value);
const errors = reflected.filter(x => !x.ok).map(x => x.reason);
Это — эквивалент allSettled, но читаемо в коде.
4) Если нужно аккумулировать ошибки и при этом продолжать выполнять следующие задачи (параллельно или последовательно)
Параллельно: используйте allSettled/reflect, потом агрегируйте результаты.Последовательно с контролем остановки по порогу ошибок:const successes = [];
const errors = [];
for (const task of tasks) {
try {
const res = await task();
successes.push(res);
} catch (err) {
errors.push(err);
if (errors.length > MAX_ERRORS) {
throw new AggregateError(errors, 'Превышен лимит ошибок, прерывание');
}
}
}
5) AggregateError
Это встроённый класс ошибок (ES2021). Удобен для бросания «нескольких» ошибок сразу:if (errors.length) throw new AggregateError(errors, 'Некоторые задачи упали');
Дополнительные замечания
Promise.all не «отменяет» другие промисы. Если вам нужно уметь отменять (например, с AbortController), вы должны реализовать отмену явно.Если вы используете await внутри map без обработки ошибок, то получите незамеченные rejected-промисы. Всегда оборачивайте в try/catch, либо работайте через allSettled/reflect.Для контроля параллелизма и удобных опций (например continueOnError) можно использовать библиотеки (p-map, Bluebird и др.), но стандартные allSettled/AggregateError покрывают большинство сценариев.Рекомендация
Если хотите дождаться всех задач и аккумулировать ошибки — используйте Promise.allSettled (или reflect). Затем обработайте списки success/error по своему сценарию (лог, повтор, агрегированная ошибка, метрика и т.д.). Если хотите прерывать при первой ошибке — используйте Promise.all (или let await внутри try/catch).