프로그래밍/JavaScript

ECMA6 - Promise

가라멜 2019. 5. 1. 13:13
반응형

출처 : https://poiemaweb.com/es6-promise

 

Promise | PoiemaWeb

Promise는 비동기 처리가 성공(fulfilled)하였는지 또는 실패(rejected)하였는지 등의 상태(state) 정보를 갖는다. Promise는 Promise 생성자를 통해 인스턴스화한다. Promise 생성자는 비동기 작업을 수행할 콜백함수를 인자로 전달받는데 이 콜백함수는 resolve와 reject 콜백함수를 인수로 전달받는다.

poiemaweb.com

 

이 포스팅의 위 출처의 게시글을 공부하며 필기 하듯이 써놓은 내용이므로,

정확한 내용은 위의 출처에서 보는 것이 좋다.


1. 프로미스란?

자바스크립트는 비동기 처리를 위한 하나의 패턴으로 콜백 함수를 사용한다. 하지만 전통적인 콜백 패턴은 가독성이 나쁘고 비동기 처리 중 발생한 에러의 예외 처리가 곤란하며 여러 개의 비동기 처리 로직을 한꺼번에 처리하는 것도 한계가 있다. 

ES6에서 비동기 처리를 위한 또 다른 패턴으로 프로미스를 도입하였다.

 

2. 콜백

2.1 콜백 헬

자바 스크립트에서 빈번하게 사용되는 비도기식 처리 모델은 요청을 병렬로 처리하여 다른 요청이 블로킹 되지 않는 장점이 있다. 

하지만 비동기 처리를 위해 콜백패턴을 ㅏ용하면 처리 순서를 보장하기 위해 여러 개의 콜백 함수가 네스팅(nesting, 중첩)되어 복잡도가 높아지느 콜백 헬이 발생하는 단점이 있다. 콜백 헬은 가독성을 나쁘게 하며 실수를 유발하는 원인이 된다.

 

2.2 에러 처리의 한계

콜백 방식의 비동기 처리가 갖는 문제점 중에서 가장 심각한 것은 에러 처리가 곤란하다는 것이다.

 

3. 프로미스의 생성

프로미스는 Promise 생성자 함수를 통해 인스턴스화한다. Promise 생성자 함수는 비동기 작업을 수행할 콜백 함수를 인자로 전달받는데 이 콜백 함수는 resolve 와 reject 함수를 인자로 전달 받는다.

const promise = new Promise((resolve, reject) => {
 // 비동기 작업 수행
 
   if(/* success */) {
     resolve('result');
   }
   else { reject('fail') 
   }
   });
  

Promise는 비동기 처리가 성공 하였는지 또는 실패 하였는지 등의 상태 정보를 갖는다.

 

pending 비동기 처리가 아직 수행되지 않은 상태 resolve 또는 reject 함수가 아직 호출되지 않은 상태
fulfilled 비동기 처리가 수행된 상태 (성공) resolve 함수가 호출된 상태
rejected 비동기 처리가 수행된 상태 (실패) reject 함수가 호출된 상태
settled 비동기 처리가 수행된 상태 (성공 또는 실패) resolve 또는 reject 함수가 호출된 상태

 

Promise 생성자 함수가 인자로 전달받은 콜백 함수는 내부에서 비동기 처리 작업을 수행한다. 이때 비동기 처리가 성공하면 콜백 함수의 인자로 전달받은 resolve 함수를 호출한다. 이때 프로미스는 'fulfilled' 상태가 된다. 비동기 처리가 실패하면 reject 함수를 호출한다. 이때 프로미스는 'rejected' 상태가 된다. 

// 비동기 함수
function get(url) {
  // Promise 객체의 생성과 반환
  return new Promise((resolve, reject) => {
    // XMLHttpRequest 객체 생성
    const xhr = new XMLHttpRequest();

    // 서버 응답 시 호출될 이벤트 핸들러
    xhr.onreadystatechange = function () {
      // 서버 응답 완료
      if (xhr.readyState === XMLHttpRequest.DONE) {
        if (xhr.status === 200) { // 정상 응답
          // resolve 메소드에 처리 결과를 전달
          resolve(xhr.response);
        } else { // 비정상 응답
          // reject 메소드에 에러 메시지를 전달
          reject('Error: ' + xhr.status);
        }
      }
    };

    // 비동기 방식으로 Request를 오픈한다
    xhr.open('GET', url);
    // Request를 전송한다
    xhr.send();
  });
}

위처럼 비동기 함수 내에서 Promise 객체를 생성하고 그 내부에서 비동기 처리를 구현한다. 이때 비동기 처리에 성공하면 resolve 메소드를 호출한다. 이때 resolve 메소드의 인자로 비동기 처리 결과로 전달한다. 이 처리 결과는 Promise 객체의 후속 처리 메소드로 전달된다. 만약 비동기 처리에 실패하면 reject 메소드를 호출한다. 이때 rejcect 메소드의 의자로 에러 메시지를 전달한다. 이 에러 메시지는 Promise 객체의 후속 처리 메소드로 전달된다.

 

4. 프로미스의 후속 처리 메소드 

Promise로 구현된 비동기 함수는 Promise 객체를 반환하여야 한다. Promise로 구현된 비동기 함수를 호출 하는 측에서는 Promise 객체의 후속 처리 메소드를 ㅗㅇ해 비동기 처리 결과 또는 에러 메시지를 전달받아 처리한다. 

- then

: 두 개의 콜백 함수를 인자로 전달받는다. 첫 번째 함수는 성공(fulfilled, resolve 함수가 호출된 상태)시 호출되고 두 번째 함수는 실패(rejceted, reject 함수가 호출된 상태) 시 호출된다.

-catch

: 예외(비동기 처리에서 발생한 에러와 then 메소드에서 발생한 에러)가 발생하면 호출된다.

 

 

5. 프로미스의 에러 처리

비동기 처리 시 발생한 에러 메시지는 then 메소드의 두 번째 콜백 함수로 전달된다. Promise 객체의 후속 처리 메소드 catch을 사용하여도 에러를 처리하 수 있다.

get(url)
  .then(result => document.getElementById('result').innerHTML = result)
  .catch(error => console.log(error));

 

catch 메소드는 에러를 처리한다는 점에서 then 메소드의 두 번째 콜백 함수와 유사하지만 미묘한 차이가 있다. then 메소드의 두 번째 콜백 함수는 비동기 처리에서 발생한 에러만을 캐치한다. 하지만 catch 메소드는 비동기 처리에서 발생한 에러뿐만 아니라 then 메소드 내부에서 발생한 에러도 캐치한다. 따라서 에러 처리는 catch 메소드를 사용하는 편이 보다 효율적이다.

 

6. 프로미스 체이닝

비동기 함수의 처리 결과를 가지고 다른 비동기 함수를 호출해야 하는 경우, 함수의 호출이 중첩되어 복잡도가 높아지는 콜백 헬이 발생한다. 프로시므는 후속 처리 메소드를 체이닝하여 여러 개의 프로미스를 연결하여 사용할 수 있다. 이로써 콜백 헬을 해결한다.

 

Promise 객체를 반환환 비동기 함수는 프로미스 후속 처리 메소드인 then이나 catch메소드를 사용할 수 있다. 따라서 then 메소드가 Promise 객체를 반환하도록 하면 여러 개의 프로미스를 연결하여 사용할 수 있다.

  const url = 'http://jsonplaceholder.typicode.com/posts';

  // 포스트 id가 1인 포스트를 검색하고 프로미스를 반환한다.
  get(`${url}/1`)
    // 포스트 id가 1인 포스트를 작성한 사용자의 아이디로 작성된 모든 포스트를 검색하고 프로미스를 반환한다.
    .then(result1 => get(`${url}?userId=${JSON.parse(result1).userId}`))
    // 포스트 검색 결과를 DOM에 반영한다.
    .then(result2 => document.getElementById('result').innerHTML = result2)
    .catch(error => console.log(error));

 

7. 프로미스의 정적 메소드

Promise 는 주로 생성자 함수로 사용되지만 함수도 객체이므로 메소드를 갖을 수 있다. Promise 객체는 4 가지 정적 메소드를 제공한다. 

 

7.1 Promise.resolve/Promise.reject

Promise.resolve와 Promise.reject 메소드는 존재하는 값을 Promise로 래핑하기 위해 사용한다.

정적 메소드 Promise.resolve 메소드는 인자로 전달된 값을 resolve 하는 Promise를 생성한다.

const resolvedPromise = Promise.resolve([1, 2, 3]);
resolvedPromise.then(console.log); // [ 1, 2, 3 ]


const resolvedPromise = new Promise(resolve => resolve([1, 2, 3]));
resolvedPromise.then(console.log); // [ 1, 2, 3 ]

Promise.reject 메소드는 인자로  전달된 값을 reject 하는 Promise를 생성한다.

const rejectedPromise = Promise.reject(new Error('Error!'));
rejectedPromise.catch(console.log); // Error: Error!

const rejectedPromise = new Promise((resolve, reject) => reject(new Error('Error!')));
rejectedPromise.catch(console.log); // Error: Error!

 

7.2 Promise.all

Promise.all 메소드는 프로미스가 담겨 있는 배열 등의 이터러블 인자로 전달 받는다. 그리고 전달받은 모든 프로미스를 병렬로 처리하고 그 처리 결과를 resolve하는 새로운 프로미스를 반환한다.

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(console.log) // [ 1, 2, 3 ]
  .catch(console.log);

Promise.all 메소드는 3 개의 프로미스를 담은 배열을 전달받았다. 각각의 프로미스는 아래와 같이 동작한다.

- 첫번째 > 3초 후에 1을 resoleve 하여 처리 결과 반환

- 두번째 > 2초부터 2을 resolve하여 처리 결과 반환

- 세번째 > 1초 후에 3을 resoleve하여 처리 결과 반환

Promise.all 메소드는 전달받은 모든 프로미스를 병렬로 처리한다. 이때 모든 프로미스의 처리가 종료될때까지 기다리 ㄴ후 아래와 모든 처리 결과를 resolve 또는 reject한다.

- 모든 프로미스의 처리가 성공하면 각각의 플미스가 resolve한 처리 결과를 배열에 담아 resolve하는 새로운프로미스를 반환한다. 이때 첫번째 프로미스가 가장 나중에 처리되어도 Promise.all 메소드가 반환하는 플미스는 첫번째 프로미스가 reslve한 처리 결과부터 차례대로 배열에 담아 그 배열을 resolve하는 새로운프로미스를 반환한다. 즉 처리 순서가 보장된다.

- 프로미스의 처리가 하나라도 실패하면 가장 먼저 실패한 프로미스가 reject 한 에러를 reject 하는 새로운 프로미스를 즉시 반환한다. 

Promise.all([
  new Promise((resolve, reject) => setTimeout(() => reject(new Error('Error 1!')), 3000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error('Error 2!')), 2000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error('Error 3!')), 1000))
]).then(console.log)
  .catch(console.log); // Error: Error 3!

 

7.3 Promise.race

Promise.race 메소드는 Promise.all 메소드와 동일하게 프로미스가 담겨 있는 배열 등의 이터러블을 인자로 전달 받는다. 그리고 Promise.race 메소드는 Promise.all 메소드처럼 모든 프로미스를 병렬 처리하는 것이 아니라 가장 먼저 처리된 프로미스가 resolve한 처리 결과를 resolve하는 새로운 프로미스를 반환한다.

 

반응형

'프로그래밍 > JavaScript' 카테고리의 다른 글

ECMA6 - iteration & for ... of  (0) 2019.05.04
ECMA6 - Symbol  (0) 2019.05.03
ECMA6 - Module  (0) 2019.04.30
ECMA6 - Class  (0) 2019.04.29
ECMA6 - 디스트럭처링  (0) 2019.04.28