이전 글에서, Promise 가 무엇이고 Promise를 이용한 비동기 처리에 대해 알아보았다.
[JavaScript] Promise와 비동기(Asnycronous) 처리
앞서, JS의 비동기 처리 및 콜백 함수에 대해서 알아보았다. [JavaScript] 싱글 쓰레드와 비동기(Asyncronous)JavaScript에서 매우 중요한 비동기 처리 방식을 공부하다 보면 JavaScript는 싱글 쓰레드 언어
junhee1203.tistory.com
이번 글에서는 Promise의 정적 메소드에 대해 알아보겠다.
참고로 Promise의 기본 개념을 알지 못한다면 이 글을 이해할 수 없다.
또한, class의 인스턴스와 정적 메소드의 차이에 대해서 기본적으로 알아야 한다.
1️⃣ Promise의 정적 메소드
알다시피 Promise는 class이므로 대게 new 키워드를 통해 인스턴스를 생성해서 사용한다.
const promise = new Promise((resolve,reject) => {})
이러한 인스턴스를 보다 유용하고 효율적으로 사용할 수 있게
Promise 자체적으로 여러 정적 메소드를 지원한다.
대표적으로 네 가지 메소드가 있다.
- Promise.all
- Promise.allSettled
- Promise.race
- Promise.any
순서대로 하나씩 알아보자.
2️⃣ Promise.all
Promise.all 은 Promise로 이루어진 배열을 인자로 받는다.
Promise.all 의 인자로 받은 Promise 배열이 모두 성공(resolve)하면 모든 resolve 결과값을 배열로 반환한다.
만약, 하나라도 실패(reject)하면 가장 먼저 실패한 Promise의 reject 값을 반환한다.
그리고, 모두 성공했을때 반환된 배열은 역시 then으로 받을 수 있고
실패했을 때 반환된 값은 catch로 받을 수 있다.
예시를 확인하자.
// 50% 확률로 Promise가 성공 or 실패
function randomGame(order) {
const random = Math.random();
return new Promise((resolve, reject) => {
if (random >= 0.5) resolve(`${order}번 게임 성공`);
else reject(`${order}번 게임 실패`);
});
}
// Promise.all 의 인자로 promise 배열 삽입
Promise.all([randomGame(1), randomGame(2), randomGame(3), randomGame(4)])
.then(console.log)
.catch(console.log)
.finally(() => console.log("--- 게임 종료 ---"));
배열 안에 Promise가 모두 성공하면, 각 Promise 마다 resolve 값을 하나의 배열에 모아 반환한다.
그리고 then 메소드를 통해 console.log 하였다.
배열 안에 Promise가 하나라도 실패했을시, 가장 먼저 실패한 Promise의 reject 값을 반환한다.
그리고 catch 메소드를 통해 console.log 하였다.
이번에는 Promise.all 을 비동기와 연관지어 사용해보자.
// setTimeout을 이용하여 외부에서 data를 받아오는 것처럼 각색한 함수
// 900ms 초과할 시 실패한 것으로 간주
function fetchData(id) {
const delay = Math.random() * 1000 ;
return new Promise((resolve, reject) => {
setTimeout(() => {
if (delay <= 900) {
resolve(
`${id}번 데이터 받아오기 성공 (${(delay).toFixed(3)}ms 소요)`
);
} else {
reject(`${id}번 데이터 받아오기 실패`);
}
}, delay);
});
}
console.log("데이터 받아오기 시작");
const startTime = Date.now();
Promise.all([fetchData(1), fetchData(2), fetchData(3), fetchData(4)])
.then((result) => {
const endTime = Date.now();
console.log(result);
console.log(`총 걸린 시간 : ${endTime - startTime}ms`);
})
.catch(console.log)
.finally(()=>console.log(`---종료---`))
여기서 Promise.all 의 강력한 기능이 발휘된다.
Promise.all 의 인자로 받은 Promise 배열의 Promise들은 동시에 실행된다.
즉, 배열의 순서대로 Promise를 실행시키지 않고 동일 출발선상에서 동시에 실행하는 것이다.
동시에 진행된 후, 실패한 Promise가 하나라도 발생하면 다른 Promise의 성공 실패와 관계없이
가장 먼저 실패한 Promise의 reject를 반환한다.
즉, 위의 실패 결과에서는 실패한 Promise 중 2번 Promise가 가장 먼저 실패한 것이다.
또 다른 실패한 Promise가 있는지는 알 수 없다. 가장 먼저 실패한 Promise만 반환하기 때문이다.
위의 예시에서는 반환된 reject를 catch로 받아 conosle.log 해주었다.
3️⃣ Promise.allSettled
Promise.allSettled 메소드는 Promise.all 과 거의 비슷하며, 마찬가지로 Promise 배열을 인자로 전해준다.
Promise.all 과 조금 다른 점은 반환값이다. 성공과 실패의 관계 없이 결과를 나타내는
일정한 형식의 객체로된 배열을 반환한다.
따라서, Promise.allSettled 메소드는 Promise 배열 안 각각의 Promise의 성공과 실패와 관계없이
동작하므로 catch 메소드로 메소드 체이닝을 해주지 않는다.
무조건, then 메소드로만 Promise.allSettled 의 결과값을 메소드 체이닝 해준다.
일정한 형식의 객체라고만 하면 감이 오지 않을 것이다.
예시를 보면 바로 감이 온다.
위의 예시를 그대로 차용하여 Promise.all -> Promise.allSettled 로만 변경해주겠다.
또한, 위에서 언급한 것처럼 catch 메소드는 필요가 없으므로 삭제해주었다.
// setTimeout을 이용하여 외부에서 data를 받아오는 것처럼 각색한 함수
// 900ms 초과할 시 실패한 것으로 간주
function fetchData(id) {
const delay = Math.random() * 1000 ;
return new Promise((resolve, reject) => {
setTimeout(() => {
if (delay <= 900) {
resolve(
`${id}번 데이터 받아오기 성공 (${(delay).toFixed(3)}ms 소요)`
);
} else {
reject(`${id}번 데이터 받아오기 실패 (${delay.toFixed(3)}ms 소요)`);
}
}, delay);
});
}
console.log("데이터 받아오기 시작");
const startTime = Date.now();
Promise.allSettled([fetchData(1), fetchData(2), fetchData(3), fetchData(4)])
.then((result) => {
const endTime = Date.now();
console.log(result);
console.log(`총 걸린 시간 : ${endTime - startTime}ms`);
})
.finally(()=>console.log(`---종료---`))
status 는 promise의 상태이다. promise의 개념을 알고 있다면 promise의 3가지 상태에 대해 알고 있을 것이다.
성공하면 value 라는 key가 생성되고 그 값으로는 resolve한 값이 할당된다.
실패하면 reason 이라는 key가 생성되고 그 값으로는 reject 한 값이 할당된다.
또한, 여기서 중요하게 볼 것은 Promise.all 과의 차이점이다.
만약 Promise.all 이었으면 위의 결과가 어떻게 되었을까 ?
바로, 실패한 Promise 중 가장 먼저 실패한 4번째 Promise의 reject 값인
'4번 데이터 받아오기 실패' 만 Promise.all의 결과로 반환됐을 것이다.
따라서, 성공한 Promise만 받아보고 싶으면 다음과 같이 응용도 가능하다.
const startTime = Date.now();
Promise.allSettled([fetchData(1), fetchData(2), fetchData(3), fetchData(4)])
.then((result) => {
const endTime = Date.now();
const successPromises = result.reduce((acc, promise) => {
if (promise.status == "fulfilled") {
acc.push(promise.value);
return acc;
} else return acc;
}, []);
successPromises.forEach(promise => console.log(promise))
console.log(`총 걸린 시간 : ${endTime - startTime}ms`);
})
.finally(() => console.log(`---종료---`));
4️⃣ Promise.any
Promise.any 역시 인자로 Promise 배열을 받는다.
그리고 성공한 Promise 중 가장 먼저 성공한 Promise의 resolve 값을 반환한다.
만약, 모두 실패하면 오류가 발생한다.
이때, 모두 실패해도 catch 메소드는 작동하지 않는다. 즉, 모두 실패하면 무조건 오류가 발생한다.
만약 오류를 핸들링 하고 싶으면 try-catch 문으로 오류를 핸들링 해야된다.
위의 Promise.all의 예시를 Promise.any로 수정해보겠다. catch 메소드는 필요없으므로 삭제한다.
// setTimeout을 이용하여 외부에서 data를 받아오는 것처럼 각색한 함수
// 900ms 초과할 시 실패한 것으로 간주
function fetchData(id) {
const delay = Math.random() * 1000 ;
return new Promise((resolve, reject) => {
setTimeout(() => {
if (delay <= 900) {
resolve(
`${id}번 데이터 받아오기 성공 (${(delay).toFixed(3)}ms 소요)`
);
} else {
reject(`${id}번 데이터 받아오기 실패`);
}
}, delay);
});
}
console.log("데이터 받아오기 시작");
const startTime = Date.now();
Promise.any([fetchData(1), fetchData(2), fetchData(3), fetchData(4)])
.then((result) => {
const endTime = Date.now();
console.log(result);
console.log(`총 걸린 시간 : ${endTime - startTime}ms`);
})
.finally(()=>console.log(`---종료---`))
결과를 보면 알 수 있듯이 가장 먼저 성공한 Promise만 반환된다.
다른 Promise의 성공 유무는 필요가 없어진다.
만약, 모든 Promise가 실패하면 어떻게 될까 ?
Promise.any 는 인자로 받은 Promise 배열 중
하나만이라도 성공하면 정상적으로 작동되는 것이다.
5️⃣ Promise.race
Promise.race 역시 Promise 배열을 인자로 받으며,
성공이든 실패든 무조건 가장 먼저 수행된 결과를 반환한다.
Promise.race는 Promise.allSettled, Promise.any 와 달리 catch문으로 실패한 값을 받을 수 있다.
즉, Promise.race 는 배열 안에 여러 Promise 중
가장 먼저 수행된 Promise의 결과를 반환하는 것이다.
심지어 그 결과가 성공이든 실패든 중요하지 않다.
위의 예시를 Promise.race로 변경해보겠다. 또한 catch 메소드를 추가하겠다.
끝으로, promise가 성공할 확률을 30%로 변경하였다.
// setTimeout을 이용하여 외부에서 data를 받아오는 것처럼 각색한 함수
// 900ms 초과할 시 실패한 것으로 간주
function fetchData(id) {
const delay = Math.random() * 1000 ;
return new Promise((resolve, reject) => {
setTimeout(() => {
if (delay <= 300) {
resolve(
`${id}번 데이터 받아오기 성공 (${(delay).toFixed(3)}ms 소요)`
);
} else {
reject(`${id}번 데이터 받아오기 실패`);
}
}, delay);
});
}
console.log("데이터 받아오기 시작");
const startTime = Date.now();
Promise.race([fetchData(1), fetchData(2), fetchData(3), fetchData(4)])
.then((result) => {
const endTime = Date.now();
console.log(result);
console.log(`총 걸린 시간 : ${endTime - startTime}ms`);
})
.catch(console.log)
.finally(()=>console.log(`---종료---`))
바로 위의 실패한 Promise가 출력된 결과를 확인하면 걸린 시간이 777ms 이다.
즉, 나머지 Promise 들은 성공인지 실패인지는 모르지만, 777ms 이상으로 시간이 소요된 것이다.
사실, 위의 코드상 300초가 넘으면 실패이기 때문에 나머지 Promise 들도 실패했을 것이다.
이번 글을 정리하겠다.
- Promise 는 대표적으로 네 가지 정적 메소드를 지원한다.
- Promise.all 은 인자의 모든 Promise 가 성공하면 resolve 값을 배열로 반환하며, 단 하나만이라도 실패했을 시 가장 먼저 실패한 Promise의 reject 값을 반환한다.
- Promise.allSettled 은 인자 내의 Promise 들의 성공 유무랑 관계 없이 Promise의 결과를 담은 일정한 객체 배열을 반환한다.
- Promise.any 는 인자 내의 Promise 들 중 가장 먼저 성공한 Promise의 resolve값을 반환한다.
- Promise.race 는 인자 내의 Promise들 중 성공 유무와 관계 없이 가장 먼저 수행된 Promise의 결과를 반환한다.
'JavaScript' 카테고리의 다른 글
[JavaScript] 옵저버(Observer) 패턴을 이용한 상태 관리 (3) | 2024.10.03 |
---|---|
[JavaScript] Promise와 비동기(Asnycronous) 처리 (0) | 2024.09.14 |
[JavaScript] drag (드래그) 관련 이벤트 전격 분석 (0) | 2024.09.10 |
[JavaScript] Element.closest 메소드 분석 (0) | 2024.08.31 |
[JavaScript] 이벤트 버블링(Bubbling), 이벤트 위임(Delegation) (2) | 2024.08.31 |