Promise.all

복수의 URL에 동시에 요청을 보내고, 다운로드가 모두 완료된 후에 콘텐츠를 처리할 때 이런 상황이 발생한다.

let promise = Promise.all([...promises...]);

Promise.all은 요소 전체가 프로미스인 배열을 받고 새로운 프로미스를 반환한다.

배열 안 프로미스가 모두 처리되면 새로운 프로미스가 이행되는데, 배열 안 프로미스의 결과값을 담은 배열이 새로운 프로미스의 result가 된다.

아래 Promise.all은 3초 후에 처리되고, 반환되는 프로미스의 result는 배열 [1 , 2 , 3]이 된다.

Promise.all([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
  new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
  new Promise(resolve => setTimeout(() => resolve(3), 1000))  // 3
]).then(alert); // 프라미스 전체가 처리되면 1, 2, 3이 반환됩니다. 각 프라미스는 배열을 구성하는 요소가 된다.

배열 result의 요소 순서는 Promise.all에 전달되는 프로미스 순서와 상응한다는 점에 주목하자!
Promise.all의 첫 번째 프로미스는 가장 늦게 이행되더라도 처리 결과는 배열의 첫 번째 요소에 저장된다.

아래는 URL이 담긴 배열을 fetch를 써서 처리하는 예시이다 ~!


let urls = [
  'https://api.github.com/users/iliakan',
  'https://api.github.com/users/Violet-Bora-Lee',
  'https://api.github.com/users/jeresig'
];

// fetch를 사용해 url을 프라미스로 매핑한다.
let requests = urls.map(url => fetch(url));

// Promise.all은 모든 작업이 이행될 때까지 기다린다.
Promise.all(requests)
  .then(responses => responses.forEach(
    response => alert(`${response.url}: ${response.status}`)
  ));

GitHub 유저네임이 담긴 배열을 사용해 사용자 정보를 가져오는 예시를 살펴보장(실무에서 id를 기준으로 장바구니 목록을 불러올 때도 같은 로직을 사용할 수 있다구 한다).

let names = ['iliakan', 'Violet-Bora-Lee', 'jeresig'];

let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));

Promise.all(requests)
	.then(respones => {
		// 모든 응답이 성공적으로 이행되었다
  		for(let response of responses ){
          alert(`${response.url} : ${response.status}`); // 모든 URL의 응답코드가 200
        }
  
  		return responses;
})
// 응답 메세지가 담긴 배열을 response.json()로 매핑해, 내용을 읽는다.
.then(responses => Promise.all(responses.map(r => r.json())))
// JSON 형태의 응답 메세지는 파싱 되어 배열 'users'에 저장된다.
.then(users => users.forEach( user => alert(user.name)))


Promise.all에 전달되는 프라미스 중 하나라도 거부되면, Promise.all이 반환하는 프라미스는 에러와 함께 바로 거부된다.


Promise.all([
	new Promise((resolve , reject) => setTimeout(() => resolve(1),1000)),
	new Promise((resolve , reject) => setTimeout(() => reject(new Error("에러발생")),2000)),
    new Promise((resolve , reject) => setTimeout(() => resolve(3), 3000))
]). catch(alert); // Error: 에러발생

2초 후에 두 번째 프로미스가 거부되면서 Promise.all 전체가 거부되고, .catch 가 실행됩니다. 거부 에러는 Promise.all 전체의 결과가 된다.

💡 에러가 발생하면 다른 프로미스는 무시된다.
: 프로미스가 하나라도 거부되면 Promise.all 은 즉시 거부되고 배열에 저장된 다른 프로미스의 결과는 완전히 무시된다. 이행된 프로미스의 결과도 무시된다.


Promise.allSettled

💡최근에 추가된 , 스펙에 추가된지 얼마 안된 문법으로 구식 브라우저는 폴리필이 필요하다.
*폴리필(polyfill)은 웹 개발에서 기능을 지원하지 않는 웹 브라우저 상의 기능을 구현하는 코드)

Promise.all 은 프로미스가 하나라도 거절되면 전체를 거절한다. 따라서, 프로미스 결과가 모두 필요할 때 같이 '모 아니면 도' 일 때 유용하다.


Promise.all([
	fetch('/test1.html'),
 	fetch('/test.css'),
 	fetch('/data.json')
]).then(render); // render 메서드는 fetch 결과 전부가 있어야 제대로 동작한다.

반면 , Promise.allSettled 는 모든 프로미스가 처리될 떄까지 기다린다. 반환되는 배열은 다음과 같은 요소를 갖는다.

  • 응답이 성공할 경우 - { status:"fulfilled", value: result }
  • 에러가 발생한 경우 - { status:"rejected" , reason: error }

fetch 를 사용해 여러 사람의 정보를 가져오고 있다고 해보자. 여러 요청 중 하나가 실패해도 다른 요청 결과는 여전히 필요하다.

이때 ! Promise.allSettled 를 사용할 수 있다.


let urls = [
  'https://api.github.com/users/iliakan',
  'https://api.github.com/users/Violet-Bora-Lee',
  'https://no-such-url'
];

Promise.allSettled(urls.map(url => fetch(url)))
   .then(results => { // (*)
	   results.forEach((result , num) => {
          if (result.status == "fulfilled"){
          	alert(`${urls[num]} : ${result.value.status}`);
          }
          if (result.status == "rejected") {
          	alert(`${urls[num]} : ${result.reason}`);
          }
       });
    });

(*)로 표시한 줄의 result 는 다음과 같을 것.


[
  {status: 'fulfilled', value: ...응답...},
  {status: 'fulfilled', value: ...응답...},
  {status: 'rejected', reason: ...에러 객체...}

]

Promise.allSettled 를 사용하면 이처럼 각 프로미스의 상태와 값 또는 에러를 받을 수 있다.


Promise.race

Promise.race 는 Promise.all 과 비슷하다. 다만 가장 먼저 처리되는 프로밋의 결과(혹은 에러)를 반환한다

*이터러블(iterable)이란 : 배열은 대표적인 이터러블이다. 배열 외에도 다수의 내장 객체가 반복 가능하다. 문자열 역시 이터러블의 예이다.

let promise = Promise.race(iterable);

아래 예시의 결과는 1 이다.

Promise.race([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("에러 발생!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1

첫번째 프로미스가 가장 빨리 처리상태가 되기 때문에 첫 번째 프로미스의 결과가 result값이 된다. 이렇게 Promise.race 를 사용하면 '경주(race)의 승자'가 나타난 순간 다른 프로미스의 결과 또는 에러는 무시된다.

 reference : 모던 자바스크립트 튜토리얼
profile
아토언니의 프론트엔드 개발자로서의 기록

0개의 댓글