여러개의 Promise를 다루는 다양한 방법 비교

js에서 비동기 호출을 다루는데엔 여러 방법이 있다.

인간 js엔진되기 2편에서도 다루었지만 더 명확하게 비교하여 정리해보려고 한다.

1. for each

여러개의 Promise가 들어있는 배열을 for each로 순회하며 호출할 수도 있다.

1
2
3
4
5
6
const urls = ["1", "2", "3", "4"];
urls.forEach(async (url) => {
const result = await fetch(url);
console.log(result.json());
});
console.log("complete");

실행결과는 다음과 같다.

1
2
3
4
5
complete
1
2
3
4

complete가 먼저 호출된 후 호출스택이 비었을 떄 1 2 3 4 순서대로 출력된다.
즉 배열 내 모든 Promise가 resolve 혹은 reject될 때까지 기다리지 않는다.

2. for await of

위와 같은 코드를 for await of 를 이용하여 작성해보자

1
2
3
4
5
6
const urls = ["1", "2", "3", "4"];
for await(let url of urls){
const result = await fetch(url);
console.log(result.json());
});
console.log("complete");

실행결과는 다음과 같다.

1
2
3
4
5
1
2
3
4
complete

1 2 3 4가 호출된 후 complete가 호출된다.
즉 배열 내 모든 Promise가 resolve혹은 reject될 때까지 기다린다.

3. Promise.all

위와 같은 코드를 Promise.all을 이용하여 작성해보자

1
2
3
4
5
6
7
8
const urls = ["1", "2", "3", "4"];
await Promise.all(
urls.map(async (ele) => {
result = await fetch(url);
console.log(result.json());
})
);
console.log("complete");

실행결과는 다음과 같다.

1
2
3
4
5
1
2
3
4
complete

즉 Promise.all도 배열 내 모든 Promise가 resolve혹은 reject될 때까지 기다린다.

하지만 for await of와 다르게 Promise의 순서를 보장하지 않는다.

아래와 같은 Timer 함수를 작성한다고 해보자

1
2
3
4
5
6
function timer(delay) {
console.log(`${delay} 타이머 시작`);
setTimeout(() => {
console.log(`${delay} 타이머 종료`);
}, delay);
}

for await of와 Promise.all에서 호출해보았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
let times = [1000, 2000, 3000, 4000];
Promise.all(times.map((time) => timer(time)));
console.log("complete");

// 1000 타이머 시작
// 2000 타이머 시작
// 3000 타이머 시작
// 4000 타이머 시작
// 1000 타이머 종료
// 2000 타이머 종료
// 3000 타이머 종료
// 4000 타이머 종료
// complete

for await (let time of times) {
timer(time);
}

// 1000 타이머 시작
// 1000 타이머 종료
// 2000 타이머 시작
// 2000 타이머 종료
// 3000 타이머 시작
// 3000 타이머 종료
// 4000 타이머 시작
// 4000 타이머 종료
// complete

즉 for await of는 Promise간의 실행순서를 보장하지만 Promise.all은 그러지 못한다.

4. Promise.allSettled

Promise.allSettled는 Promise.all과 같은 기능을 하지만 Promise.all은 부분적으로 실패할 경우에 배열 전체의 실행이 중지되고 catch문으로 이동한다.
하지만 allSettled는 배열의 모든 Promise를 실행하고 상태와 결과값을 하나의 객체로 resolve문에 넘긴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) =>
setTimeout(reject, 100, "foo")
);
const promises = [promise1, promise2];

Promise.allSettled(promises).then((results) =>
results.forEach((result) => console.log(result))
);

// {status:'fulfilled', value:3}
// {status:'rejected',reason:'foo'}

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) =>
setTimeout(reject, 100, "foo")
);
const promises = [promise1, promise2];

Promise.all(promises)
.then((results) => results.forEach((result) => console.log(result.status)))
.catch((error) => console.log(error));

// foo

정리하자면 Promise.all은 일부 Promise에서 에러가 발생할 경우 모든 Promise의 실행을 중지하고 전부 에러를 발생시킨다.

하지만 Promise.allSettled는 일부 Promise에서 에러가 발생하더라도 모든 Promise가 실행될 때까지 기다린 후 Promise배열의 상태와 결과를 리턴한다.

5. Promise.race

Promise.race는 가장 먼저 완료된 Promise의 값으로 Promise를 reject하거나 resolve한다.

1
2
3
4
5
6
7
8
9
10
11
12
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, "one");
});

const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "two");
});

Promise.race([promise1, promise2]).then((value) => {
console.log(value);
// two
});

정리

순서가 보장되어야 하는가? => for await of
일부 Promise에서 에러가 발생할 경우 전체의 실행을 멈추고 싶은가? => Promise.all
일부 Promise에서 에러가 발생하더라도 에러가 발생한 일부 Promise만 따로 예외처리 해주고 싶은가? => Promise.allSettled
가장 먼저 실행이 된 Promise의 결과만 다루고 싶은가? => Promise.race

댓글