Promise는 JavaScript의 비동기 처리를 위한 핵심 개념입니다. 특히 여러 개의 비동기 작업을 동시에 처리할 때 Promise.all과 Promise.allSettled를 자주 사용하게 되는데, 이들이 내부적으로 어떻게 동작하는지 직접 구현해보면서 이해해보겠습니다.
먼저 많은 개발자들이 헷갈려하는 부분부터 살펴보겠습니다.
// 이렇게 하면 순차 실행됩니다
const sequentialExecution = async (promises) => {
const results = []
for (let i = 0; i < promises.length; i++) {
const value = await Promise.resolve(promises[i]) // 하나씩 기다림
results[i] = value
}
return results
}
async/await은 코드가 깔끔하지만 await 키워드가 해당 Promise가 완료될 때까지 기다리기 때문에 순차적으로 실행됩니다.
// .then()을 사용한 병렬 실행
const parallelExecution = (promises) => {
return new Promise((resolve, reject) => {
const results = new Array(promises.length)
let completed = 0
promises.forEach((promise, index) => {
Promise.resolve(promise) // 즉시 시작
.then(value => {
results[index] = value
completed++
if (completed === promises.length) {
resolve(results)
}
})
.catch(reject)
})
})
}
.then() 방식에서는 모든 Promise가 동시에 시작되고 각각 독립적으로 완료됩니다.
Promise.all의 특징을 먼저 정리해보겠습니다:
const promiseAll = (promises) => {
return new Promise((resolve, reject) => {
// 빈 배열 처리
if (promises.length === 0) {
return resolve([])
}
const results = new Array(promises.length)
let completed = 0
promises.forEach((promise, index) => {
Promise.resolve(promise) // Promise가 아닌 값도 처리
.then(value => {
results[index] = value // 순서 보장
completed++
if (completed === promises.length) {
resolve(results)
}
})
.catch(reject) // 하나라도 실패하면 즉시 reject
})
})
}
Promise.allSettled는 Promise.all과 다르게 모든 Promise의 완료를 기다립니다:
const promiseAllSettled = (promises) => {
return new Promise((resolve) => {
if (promises.length === 0) {
return resolve([])
}
const results = new Array(promises.length)
let completed = 0
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(value => {
results[index] = { status: 'fulfilled', value }
completed++
if (completed === promises.length) {
resolve(results)
}
})
.catch(reason => {
results[index] = { status: 'rejected', reason }
completed++
if (completed === promises.length) {
resolve(results)
}
})
})
})
}
{ status, value/reason } 형태로 반환구현한 함수들이 제대로 동작하는지 확인해보겠습니다:
const runTests = async () => {
console.log('=== Promise.all 테스트 ===')
// 성공 케이스
try {
const result1 = await promiseAll([
Promise.resolve(1),
2, // Promise가 아닌 값
Promise.resolve(3)
])
console.log('✅ 성공 케이스:', result1) // [1, 2, 3]
} catch (error) {
console.log('❌ 예상치 못한 에러:', error)
}
// 실패 케이스
try {
const result2 = await promiseAll([
Promise.resolve(1),
promiseReject('error'),
Promise.resolve(3)
])
console.log('❌ 실패해야 하는데 성공:', result2)
} catch (error) {
console.log('✅ 실패 케이스 정상:', error) // "error"
}
// allSettled 테스트
const result3 = await promiseAllSettled([
Promise.resolve(1),
promiseReject('error'),
Promise.resolve(3)
])
console.log('✅ allSettled 결과:', result3)
// [
// { status: "fulfilled", value: 1 },
// { status: "rejected", reason: "error" },
// { status: "fulfilled", value: 3 }
// ]
}
runTests()
이번 포스트에서 다룬 내용들을 정리하면:
.then()은 병렬 실행forEach로 동시 시작, 카운터로 완료 확인, 인덱스로 순서 보장Promise의 내부 동작을 이해하면 비동기 코드를 더 효율적으로 작성할 수 있습니다. 특히 여러 API 호출을 동시에 처리할 때 이런 지식이 큰 도움이 됩니다.
실제 프로젝트에서는 내장된 Promise.all과 Promise.allSettled를 사용하시면 되지만, 이렇게 직접 구현해보는 과정을 통해 Promise의 동작 원리를 깊이 이해할 수 있었습니다.