본문으로 건너뛰기
김신건의 로그

[Javascript] Promise.all / race / any / allSettled

· 수정 · 📖 약 1분 · 368자/단어 #javascript #promise #async #concurrency
settled, JS Promise.all, Promise.race, Promise.any, Promise.allSettled

정의

Promise 의 정적 메서드 4 형제. 여러 Promise 를 동시에 다루는 도구.

메서드fulfilled 조건rejected 조건결과
all모두 fulfilled하나라도 rejected값 배열 / 첫 rejection
allSettled모두 정착절대 안 됨{status, value/reason} 배열
race가장 먼저 fulfilled가장 먼저 rejected첫 정착값/사유
any하나라도 fulfilled모두 rejected첫 fulfilled / AggregateError

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()),
]);

모두 성공해야 결과. 하나라도 실패하면 즉시 throw, 나머지는 계속 실행되지만 결과 못 받음.

try {
    const results = await Promise.all([p1, p2, p3]);
} catch (e) {
    // 가장 먼저 실패한 reason
}

Promise.allSettled

const results = await Promise.allSettled([p1, p2, p3]);
results.forEach((r, i) => {
    if (r.status === 'fulfilled') console.log(i, r.value);
    else console.log(i, r.reason);
});

모두 정착 (성공/실패 무관) 까지 대기. 부분 실패가 OK 일 때.

Promise.race

// 타임아웃 패턴
function timeout(ms) {
    return new Promise((_, reject) =>
        setTimeout(() => reject(new Error('timeout')), ms));
}

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

가장 먼저 정착 (성공/실패 무관) 한 Promise 결과.

Promise.any (ES2021)

// 여러 CDN mirror 중 하나라도 응답하면
const data = await Promise.any([
    fetch('https://mirror1/data'),
    fetch('https://mirror2/data'),
    fetch('https://mirror3/data'),
]);

하나라도 성공 하면 그 값. 모두 실패하면 AggregateError (errors 배열).

try {
    const data = await Promise.any([...]);
} catch (e) {
    if (e instanceof AggregateError) {
        e.errors.forEach(err => console.error(err));
    }
}

비교 매트릭스

상황권장
모두 필요all
부분 실패 OKallSettled
가장 빠른 응답 (성공/실패 무관)race
하나라도 성공any
타임아웃race + timeout Promise

흔히 만나는 패턴

concurrent fetch

const users = await Promise.all(
    userIds.map(id => fetch(`/api/users/${id}`).then(r => r.json()))
);

배열의 map 콜백에서 Promise 반환 → all 로 모음.

concurrent with limit

// 동시 5 개로 제한
async function pool(items, fn, concurrency = 5) {
    const results = [];
    for (let i = 0; i < items.length; i += concurrency) {
        const chunk = items.slice(i, i + concurrency);
        const r = await Promise.all(chunk.map(fn));
        results.push(...r);
    }
    return results;
}

await pool(urls, fetch, 5);

라이브러리: p-limit, p-queue.

timeout wrapper

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

const r = await withTimeout(fetch('/api'), 5000);

부분 실패 통계

const results = await Promise.allSettled(
    items.map(item => process(item))
);
const success = results.filter(r => r.status === 'fulfilled').length;
const failed = results.length - success;
console.log(`${success}/${results.length} succeeded`);

함정

1. Promise.all 의 fail-fast

Promise.all([slow_p, fail_p])
// fail_p 가 즉시 throw → slow_p 의 결과는 못 받음
// 그러나 slow_p 는 background 에서 계속 실행됨 (cancel 안 됨)

진짜 cancel 은 AbortController 사용.

2. 빈 배열

Promise.all([])             // [] (즉시 resolve)
Promise.allSettled([])      // []
Promise.race([])             // 영원히 pending ⚠️
Promise.any([])              // AggregateError

race / any 에 빈 배열 주의.

3. await Promise.all vs 순차

// ❌ 순차 (2 sec)
const a = await fetch('/a');   // 1 sec
const b = await fetch('/b');   // 1 sec

// ✓ 동시 (1 sec)
const [a, b] = await Promise.all([
    fetch('/a'),
    fetch('/b'),
]);

의존성 없으면 항상 all 로 묶기.

4. 큰 배열의 메모리

Promise.all(arr.map(fn))     // 100,000 개면 모두 동시 시작

concurrency limit 권장.

참고

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기