본문 바로가기

Javascript

자바스크립트 Promise

반응형

Promise 객체

프로미스 객체는 비동기 통신에 이용되는 객체이다.

 

프로미스 객체 등장 전에 자바스크립트는 콜백함수를 통해 비동기 통신을 처리했다. 그런데 서비스의 규모가 커질수록, 콜백함수를 통한 비동기 통신 처리는 여러 한계를 마주쳤다. 대표적인 예로, 콜백함수가 계속해서 콜백함수를 낳는 콜백 헬애매한 에러 처리가 있다. 그래서 콜백 헬을 없애고, 더 명확한 비동기 통신의 표현을 위해 ES6에서 프로미스 객체가 도입되었다.

비동기 통신을 callback으로 처리했을 때 문제점

콜백 헬(callback hell)

자바스크립트에서 자주 사용되는 비동기 통신은 각 요청을 병렬로 처리한다. 덕분에 프로그램이 Blocking되지 않는(중단되지 않는) 장점이 있다. 그러나 비동기 통신을 올바르게 처리하기 위해서는 응답이 왔을 때의 후속 처리를 콜백 함수로 작성해야한다.

 

그렇다면, 현재 실행 중인 비동기 통신의 후속처리를 또다른 비동기 통신이 해야한다면? 이때 콜백 헬이 발생한다.

 

step1(function (value1) {
  step2(value1, function(value2) {
    step3(value2, function(value3) {
      step4(value3, function(value4) {
        step5(value4, function(value5) {
            // value5를 사용하는 처리
        });
      });
    });
  });
});

 

위 코드처럼 우리는 value5를 위한 처리를 위해 총 5번의 콜백 함수를 거쳐야 한다. 가독성도 너무 안좋고 depth도 말이 안된다 🥲. 저 안에 몇몇 코드가 더 추가된다면 실수를 유발할 가능성이 크다.

에러 처리의 한계

try {
  setTimeout(() => { throw new Error('Error!'); }, 1000);
} catch (e) {
  console.log('에러를 캐치하지 못한다..');
  console.log(e);
}

 

콜백 함수를 통한 비동기 통신의 처리는 에러 처리도 어렵다. 위 코드는 다음 과정처럼 실행된다.

 

    1. try...catch...문이 실행된다.

    2. setTimeout 함수가 실행된다.

    3. setTimeout 함수는 브라우저에게 타이머 이벤트를 요청한 후 바로 종료된다.

    4. 1000ms 후, setTimeout함수의 콜백함수는 Task Queue로 이동한다

    5. 호출 스택이 비었는지 계속 확인하던 이벤트 루프는 호출 스택이 빈 것을 보고 setTimeout 콜백함수를 호출 스택에 넣고 실행시킨다.

    6. 에러가 발생한다.

    7. setTimeout 함수는 이미 종료되었다. 에러를 캐치할 수 없다.

 

예외(exception)는 호출자(caller) 방향으로 전파된다.하지만 setTimeout 콜백함수가 호출됐을 때, setTimeout 함수는 이미 종료된 이후다 (호출 스택에서 제거되었다). 따라서 catch 블록에서 에러가 캐치되지 않아 프로세스는 종료된다.

Promise의 동작

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

 

const promise = new Promise((resolve, reject) => {
  // 비동기 통신 로직
  // pending

  if (/* 비동기 작업 수행 성공 */) {
    resolve('result'); // pending -> fulfiiled
  }
  else { /* 비동기 작업 수행 실패 */
    reject('failure reason'); // pending -> rejected
  }
});

 

프로미스 객체는 pending, fulfilled, rejected 중 한 상태를 가진다. 처음에는 pending이었다가 비동기 처리가 성공이면 resolve함수를 호출하고 fullfilled 상태가 된다. 비동기 처리가 실패하면 reject 함수를 호출한다. 이때 프로미스는 rejected 상태가 된다.

Promise의 후속 처리 메소드

프로미스에서 실행되는 비동기 함수는 프로미스를 반환한다. 따라서 우리는 프로미스 후속 처리 메소드인 then, catch를 통해 응답받은 프로미스를 처리한다.

then

then 메소드는 두 개의 콜백 함수를 인자로 전달 받는다. 첫 번째 콜백 함수는 성공 비동기 통신 성공시 호출되고, 두 번째 함수는 실패시 호출된다. then 메소드는 Promise를 반환한다.

 

fetch(올바른 url)
  .then(res => console.log("success"), err => console.error(er)); // success
  
fetch(잘못된 url)
  .then(res => console.log("success"), err => console.error(err)); // Error: 404

catch

catch 메소드는 예외 발생시 호출된다. 콜백 함수가 실패했을 때 일수도 있고, then 메소드 내부에서 예외가 발생할 때 일 수도 있다. catch 메소드는 Promise를 반환한다.

 

fetch(잘못된 url)
  .then(res => console.log("success"))
  .catch(err => console.error(err)); // Error: 404

Promise 체이닝

비동기 함수의 결과를 가지고 다시 비동기 함수 요청을 해야하는 경우 사용한다. 이로써 콜백 헬을 해결할 수 있다.

 

fetch(올바른 url)
      .then(res => fetch(올바른 url2))
      .then(res => fetch(올바른 url2))
      .then(res => conosole.log(res))
      .catch(console.error);

 

Promise 메소드

Promise.all

전달받은 모든 Promise를 병렬로 처리한다. 각각의 프로미스가 resolve한 처리 결과를 배열에 담아 resolve하는 새로운 프로미스를 반환한다. 처리 순서가 보장된다.

 

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

Promise.race

전달받은 모든 Promise를 병렬로 처리한다. 가장 먼저 처리된 프로미스가 resolve한 처리 결과를 resolve하는 새로운 프로미스를 반환한다.

 

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

 

 

 

🤔 느낀점

자바스크립트를 배우고 얼마되지 않아서 프로미스 객체의 존재를 알았다. 그 때 무작정 프로미스가 뭔지도 모르면서 사용했던 기억이 있다. 가장 기억에 남는 부분이 내가 원하는 결과를 갖기위해서 then,,,then,,,then,,,했던 기억이 난다. fetch와 axios를 당연하게 사용하는 요즘, 이 함수들 또한 Promise를 반환하는데 정작 Promise를 이제 공부하다니...조금 반성하게 된다.

 

 

출처

https://poiemaweb.com/es6-promise

반응형

'Javascript' 카테고리의 다른 글

자바스크립트 Shallow Copy vs Deep Copy  (0) 2021.10.02
자바스크립트 Strict mode  (1) 2021.09.24
자바스크립트 Hoisting  (0) 2021.09.19
자바스크립트 Prototype  (0) 2021.09.19
함수 선언문 vs 함수 표현식  (0) 2021.04.27