Урок 04

Промисы: комбинаторы

Для работы с несколькими промисами есть четыре метода.

Promise.all

Ждёт все промисы. Возвращает массив результатов в том же порядке. Если хотя бы один упадёт — весь промис упадёт.

const [users, posts, comments] = await Promise.all([
    fetch('/api/users').then((r) => r.json()),
    fetch('/api/posts').then((r) => r.json()),
    fetch('/api/comments').then((r) => r.json()),
]);

Все три запроса идут параллельно. Общее время — самый долгий запрос, а не сумма.

Использование:

Promise.all([p1, p2, p3])
    .then(([r1, r2, r3]) => console.log(r1, r2, r3))
    .catch((err) => console.error('Один упал:', err));

Promise.allSettled

Ждёт все промисы, даже если некоторые упадут. Возвращает массив объектов со статусом.

const results = await Promise.allSettled([p1, p2, p3]);

// [
//   { status: 'fulfilled', value: ... },
//   { status: 'rejected', reason: ... },
//   { status: 'fulfilled', value: ... }
// ]

Обработка:

results.forEach((result) => {
    if (result.status === 'fulfilled') {
        console.log('Успех:', result.value);
    } else {
        console.error('Ошибка:', result.reason);
    }
});

Когда использовать: когда нужны все результаты, и падение одного не должно ломать остальные.

Promise.race

Возвращает результат первого завершённого промиса — успешного или упавшего.

const result = await Promise.race([
    fetch('/api/fast'),
    fetch('/api/slow'),
]);

Использование: таймауты.

function withTimeout(promise, ms) {
    return Promise.race([
        promise,
        new Promise((_, reject) =>
            setTimeout(() => reject(new Error('Timeout')), ms)
        ),
    ]);
}

await withTimeout(fetchData(), 5000);

Promise.any

Возвращает первый успешный промис. Игнорирует упавшие.

const result = await Promise.any([
    fetch('/api/mirror-1'),
    fetch('/api/mirror-2'),
    fetch('/api/mirror-3'),
]);

Если все упадут — AggregateError:

try {
    const result = await Promise.any([p1, p2]);
} catch (err) {
    if (err instanceof AggregateError) {
        console.error('Все упали:', err.errors);
    }
}

Сравнение

МетодЖдётУспехОшибка
allвсемассивпервый reject
allSettledвсемассив со статусаминикогда
raceпервыйпервыйпервый reject
anyпервый успешныйпервыйвсе reject

Последовательные vs параллельные

Последовательно (медленно):

const a = await fetchA();
const b = await fetchB();
const c = await fetchC();
// время = a + b + c

Параллельно (быстро):

const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);
// время = max(a, b, c)

Ограничение параллельности

Если запросов сотни — не запускай все сразу:

async function mapLimit(items, limit, fn) {
    const results = [];
    const executing = new Set();

    for (const item of items) {
        const p = fn(item).then((r) => {
            executing.delete(p);
            return r;
        });

        results.push(p);
        executing.add(p);

        if (executing.size >= limit) {
            await Promise.race(executing);
        }
    }

    return Promise.all(results);
}

Итоги

  • Promise.all — все, один упал — все упали
  • allSettled — все, даже с ошибками
  • race — первый завершённый
  • any — первый успешный
  • Параллельно быстрее последовательного
  • Ограничивай параллельность при нагрузке