Проанализируйте следующий JavaScript-кейс: const results = await Promise.all(tasks.map(async t => await fetch(t))); — как обрабатывать ошибки отдельных промисов, чтобы один провал не прервал все операции, и какие альтернативы Promise.all есть для частичной устойчивости?
Коротко: `Promise.all` завершится с ошибкой при первом отклонённом (rejected) промисе. Чтобы один провал не прерывал все операции, нужно превратить каждый промис в «безопасный» — тот, который всегда резолвится с метаданными об успехе/ошибке — или использовать `Promise.allSettled`. Ниже практичные варианты и пояснения. 1) Обернуть каждый промис и вернуть объект статуса - паттерн: каждый промис `.then(...).catch(...)` превращается в `{ status: 'fulfilled', value }` или `{ status: 'rejected', reason }`. Пример: ```js const wrapped = tasks.map(t =>
fetch(t) .then(res => { if (!res.ok) throw new Error(`HTTP ${res.status}`); return res; }) .then(value => ({ status: 'fulfilled', value })) .catch(reason => ({ status: 'rejected', reason })) ); const results = await Promise.all(wrapped); // потом фильтруете: const success = results.filter(r => r.status === 'fulfilled').map(r => r.value); const failed = results.filter(r => r.status === 'rejected').map(r => r.reason); ``` Преимущество: полный контроль, можно вернуть запасные значения, логировать ошибки, считать число падений и т.д. 2) Использовать Promise.allSettled (чуть короче) ```js const settled = await Promise.allSettled(tasks.map(t => fetch(t))); // каждая запись: { status: 'fulfilled', value } или { status: 'rejected', reason } ``` Учтите: `fetch` не считает HTTP-ошибки (4xx/5xx) как rejection — нужно проверять `res.ok` внутри `then`. 3) Обработка в асинхронных функциях (try/catch внутри map) ```js const promises = tasks.map(async t => { try { const res = await fetch(t); if (!res.ok) throw new Error(`HTTP ${res.status}`); return { status: 'fulfilled', value: res }; } catch (reason) { return { status: 'rejected', reason }; } }); const results = await Promise.all(promises); ``` Это то же самое, но более читабельно при сложной логике. 4) Альтернативы для других требований отказоустойчивости - `Promise.any` — возвращает первый успешный результат; отклоняется только если все промисы отклоняются. - `Promise.race` — вернёт первый settled (fulfill или reject); не даёт частичной устойчивости по умолчанию. - Конкурентный лимит (p-limit, p-map, Bluebird.map) — уменьшает нагрузку/таймауты; можно оборачивать каждый вызов в таймаут/abort. - Bluebird: `.reflect()` и `.settle()` дают похожую семантику на `allSettled`. 5) Полезные замечания - Проверяйте `response.ok` у `fetch`, иначе 4xx/5xx не попадут в catch. - При необходимости агрегировать ошибки — используйте `AggregateError` или соберите массив причин и бросьте вручную. - Если нужно считать допустимое число ошибок, используйте `Promise.allSettled`, посчитайте `rejected` и бросьте, если превысило порог. Резюме: для простого и надежного поведения используйте `Promise.allSettled` или оборачивайте каждый промис через `.catch`/try–catch, чтобы гарантировать, что один провал не прерывает все операции.
1) Обернуть каждый промис и вернуть объект статуса
- паттерн: каждый промис `.then(...).catch(...)` превращается в `{ status: 'fulfilled', value }` или `{ status: 'rejected', reason }`.
Пример:
```js
const wrapped = tasks.map(t => fetch(t)
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res;
})
.then(value => ({ status: 'fulfilled', value }))
.catch(reason => ({ status: 'rejected', reason }))
);
const results = await Promise.all(wrapped);
// потом фильтруете:
const success = results.filter(r => r.status === 'fulfilled').map(r => r.value);
const failed = results.filter(r => r.status === 'rejected').map(r => r.reason);
```
Преимущество: полный контроль, можно вернуть запасные значения, логировать ошибки, считать число падений и т.д.
2) Использовать Promise.allSettled (чуть короче)
```js
const settled = await Promise.allSettled(tasks.map(t => fetch(t)));
// каждая запись: { status: 'fulfilled', value } или { status: 'rejected', reason }
```
Учтите: `fetch` не считает HTTP-ошибки (4xx/5xx) как rejection — нужно проверять `res.ok` внутри `then`.
3) Обработка в асинхронных функциях (try/catch внутри map)
```js
const promises = tasks.map(async t => {
try {
const res = await fetch(t);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return { status: 'fulfilled', value: res };
} catch (reason) {
return { status: 'rejected', reason };
}
});
const results = await Promise.all(promises);
```
Это то же самое, но более читабельно при сложной логике.
4) Альтернативы для других требований отказоустойчивости
- `Promise.any` — возвращает первый успешный результат; отклоняется только если все промисы отклоняются.
- `Promise.race` — вернёт первый settled (fulfill или reject); не даёт частичной устойчивости по умолчанию.
- Конкурентный лимит (p-limit, p-map, Bluebird.map) — уменьшает нагрузку/таймауты; можно оборачивать каждый вызов в таймаут/abort.
- Bluebird: `.reflect()` и `.settle()` дают похожую семантику на `allSettled`.
5) Полезные замечания
- Проверяйте `response.ok` у `fetch`, иначе 4xx/5xx не попадут в catch.
- При необходимости агрегировать ошибки — используйте `AggregateError` или соберите массив причин и бросьте вручную.
- Если нужно считать допустимое число ошибок, используйте `Promise.allSettled`, посчитайте `rejected` и бросьте, если превысило порог.
Резюме: для простого и надежного поведения используйте `Promise.allSettled` или оборачивайте каждый промис через `.catch`/try–catch, чтобы гарантировать, что один провал не прерывает все операции.