본문 바로가기

Front-end/JavaScript

[JavaScript] 프라미스 API

Promise 클래스에는 5가지 정적메소드가 있다. 

1. Promise.all

만약 여러개의 프로미스를 동시에 실행시키고 모든 프로마스가 준비될 때가지 기다려야한다면?

( 여러개의 URL에 동시에 요청을 보내고 다운로드가 모두 완료된 이후에 필요한 콘텐츠를 실행시키는 경우)

Promise.all을 사용할 수 있다.

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

Promise.all은 요소 전체가 프로미스 배열을 받고, 새로운 프로미스를 반환한다. 배열 안에 프로미스가 모두 실행이 되면 새로운 프로미스가 이행되는데, 배열 안 프라미스의 결괏값을 담은 배열이 새로운 프라미스의 result가 된다.

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);

위의 코드는 3초 뒤(가장 마지막까지 실행되는 프라미스가 끝나는 시점)에 [1,2,3] 이 반환된다.

또한 프로미스 배열의 result값이 담긴 배열은 실행이 된 순서대로 담긴다. (맨 첫번째 프로미스가 가장 늦게 실행되더라도, result 배열에는 가장 먼저 담김)

 

작업해야 할 데이터가 담긴 배열을 프라미스가 담긴 배열로 매핑하고, 이 배열을 Promise.all로 감싸는 방법은 자주 사용된다.

// Githurb api를 이용해서 유저정보를 가져와야 할 때
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}`)
  ));
//유저네임만 담긴 배열을 사용해서 유저 정보를 가져오는 예
let names = ['iliakan', 'Violet-Bora-Lee', 'jeresig'];

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

Promise.all(requests)
  .then(responses => {
    // 모든 응답이 성공적으로 이행되었습니다.
    for(let response of responses) {
      alert(`${response.url}: ${response.status}`); // 모든 url의 응답코드가 200입니다.
    }

    return responses;

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초 후에 에버가 발생하면서 .catch문으로 넘어가고 Promise.all 전체가 거부되면서 이때의 거부에러는 Promise.all 전체의 결과가 된다.

더보기

❌ 에러가 발생하면 다른 프로미스는 무시된다. 

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

--> 위의 2개 프로미스 중에 두번째 프로미스에서 거부되었지만, 실행은 3번째 프로미스까지 이행된다. 

프라미스에는 '취소’라는 개념이 없어서 Promise.all도 프라미스를 취소하지 않기 때문이다.

2. Promise.allSettled

더보기

추가된 지 얼마 안된 문법임

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

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

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

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

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

let urls = [
  'https://api.github.com/users/현지',
  'https://api.github.com/users/현욱',
  'https://no-such-url'
];

Promise.allSettled(urls.map(url => fetch(url)))
  .then(results => { 
    results.forEach((result, index) => {
      if (result.status == "fulfilled") {
        alert(`${urls[index]}: ${result.value.status}`);
      }
      if (result.status == "rejected") {
        alert(`${urls[index]}: ${result.reason}`);
      }
    });
  });
이때 result의 각 원소는 아래와같은 값을 반환한다.
[
  {status: 'fulfilled', value: ...응답...},
  {status: 'fulfilled', value: ...응답...},
  {status: 'rejected', reason: ...에러 객체...}
]

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

 

3. Promise.race

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

let promise = Promise.race(iterable);
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

위 코드의 결과는 1이다. 

첫 번째 프라미스가 가장 빨리 처리상태가 되기 때문에 첫 번째 프라미스의 결과가 result 값이 되는 것이다.

이렇게 Promise.race를 사용하면 '경주(race)의 승자’가 나타난 순간 다른 프라미스의 결과 또는 에러는 무시된다.

 

4. Promise.resolve & Promise.reject

프라미스 메서드 Promise.resolve와 Promise.reject는 async/await 문법이 생긴 후로 쓸모없어졌기 때문에 근래에는 거의 사용하지 않는다.

** Promise.resolve

Promise.resolve(value)는 결괏값이 value인 이행 상태 프라미스를 생성한다. 또한 함수가 프라미스를 반환하도록 해야 할 때 사용할 수 있다.

 

아래 함수 loadCached는 인수로 받은 URL을 대상으로 fetch를 호출하고, 그 결과를 기억(cache)한다.

나중에 동일한 URL을 대상으로 fetch를 호출하면 캐시에서 호출 결과를 즉시 가져오는데, 이때 Promise.resolve를 사용해 캐시 된 내용을 프라미스로 만들어 반환 값이 항상 프라미스가 되게 한다.

let cache = new Map();

function loadCached(url) {
  if (cache.has(url)) {
    return Promise.resolve(cache.get(url)); // (*)
  }

  return fetch(url)
    .then(response => response.text())
    .then(text => {
      cache.set(url,text);
      return text;
    });
}

 

loadCached를 호출하면 이행상태의 프라미스가 반환된다는 것이 보장되기 때문에 loadCached(url).then(…)을 사용할 수 있다. 

-> .then은 프라미스가 성공했을 때에만 사용할 수 있기 때문에 반드시 성공한 프라미스를 반환하는 loadCached에서 .then을 사용할 수 있다는 뜻이다.

 

** Promise.reject

Promise.reject(error)는 결괏값이 error인 거부 상태 프라미스를 생성한다.

아래 코드와 동일한 일을 수행한다.

let promise = new Promise((resolve, reject) => reject(error));
//실무에서 쓰일 일은 거의 없다고한다.