호다닥 톺아보는 Promise

콜백지옥에서 벗어나기

지난 포스팅에서 Callback함수란 무엇인가에 대해서 다뤘고, 복잡한 로직에서 Callback함수를 사용할때의 문제점, 콜백지옥에 대해서도 다뤘습니다.

콜백지옥을 짧게 요약하자면 다음과 같습니다.

  1. 코드의 가독성이 떨어진다.
  2. 매 코드블럭들마다 에러처리를 해주어야 한다.

Promise?

어떤 특정한 문법이 아니라 일종의 패턴을 나타내는 용어였던 Callback과 달리 Promise는 Javascript의 객체입니다.

img

간단히 말하자면 비동기로 실행된 작업의 결과를 나타내는 객체의 이름이라고 보시면 됩니다.

Promise 만들기!

간단히 아래와 같이 Promise 생성자로 만들 수 있습니다.

let promise = new Promise(function(resolve, reject) {
  // Doing Something!
});

Promise객체는 파라미터로 executor라는 함수를 받아서 비동기로 실행하고, 성공이든 실패든 값을 반환하게 됩니다.
image

executor는 두개의 Callback 파라미터를 갖고 있습니다.

  • resolve(value) : 성공시, value를 반환하는 함수 호출
  • reject(error) : 실패시, error를 반환하는 함수 호출

Promise의 4가지 상태

성공과 실패가 있으니 당연히 Promise라는 객체가 갖고있는 상태가 있겠죠?

img

크게는 성공(fulfilled)과 실패(rejected)가 있고, 실행중인 상태(pending)과 실행이 끝났을 때(settled)가 있습니다.

조금 더 자세히 설명하자면,

  • pending : resolve(value)또는 reject(error)가 호출되기 전, 실행중인 상태
  • fulfilled : resolve(value)가 호출된 상태. 성공적으로 실행.
  • rejected : reject(error)가 호출된 상태. 실행중 실패.
  • settled : 성공/실패 상태유무와 별개로 모든 프로세스가 종료된 상태.

아래 예시코드에서는 Promise생성자로 새로운 Promise를 만들어 성공적으로 일을 마쳤다는 것을 가정해 resolve함수를 호출하고 있습니다.

let p = new Promise(function(resolve, reject) {
  // Doing something!
  resolve(1);
});

console.log(p);

그렇게 반환된 Promise객체는 변수 “p”에 저장되고, 그것을 출력해보면

Promise {[[PromiseState]]: 'fulfilled', [[PromiseResult]]: 1}

상태는 fulfilled, 반환된 값은 resolve함수에 넣었던 “1”이 담겨있습니다.

이번엔 작업이 실패했음을 가정해서 reject함수를 호출해보겠습니다.

let p = new Promise(function(resolve, reject) {
  // Doing something!
  reject("ERROR!!!!!!!");
});

console.log(p);
Promise {[[PromiseState]]: 'rejected', [[PromiseResult]]: 'ERROR!!!!!!!'}

이번엔 상태가 rejected로 뜨고 reject함수에 넣었던 에러메세지가 담겨있습니다!

then, catch, finally

그런데 사실 Promise로 실행된 값을 정상적으로 받아보려면 console.log가 아니라 다른 방법을 사용해야 합니다.

왜냐면 Promise는 비동기작업이기 때문이죠!

이전 Callback함수에서 체인을 만들어 비동기작업의 후속작업을 확정적으로 실행할 수 있게 했듯이,
Promise도 작업들을 연결해주는 메서드가 있습니다.

then

then은 이전 Promise객체를 받아서 처리해주는 구문입니다.

let p = new Promise(function(resolve, reject) {
  // Doing something!
});

p.then(
  res => {/* promise resolved */},
  err => {/* promise rejected */}
);

then은 두개의 Callback함수를 인자로 받습니다.

  • 첫번째 인자는 promise가 정상적으로 실행되어 값을 반환한 경우 (resolved)
  • 두번째 인자는 promise가 비정상적으로 실행되어 에러값을 반환한 경우(rejected)

예제로 보면:

let p = new Promise(function(resolve, reject) {
  // Doing something!
  resolve(1);
});

p.then(
  res => {
    /* promise resolved */
    console.log(res);  //1
  },
  err => {/* promise rejected */}
);

첫번째 인자를 받아서 출력했더니, Promise에서 정상적으로 실행되었다고 가정하여 호출된 resolve함수에 담긴 값이 출력되었습니다.

이번엔 실패하였다고 가정하여 error를 출력해보겠습니다.

let p = new Promise(function(resolve, reject) {
  // Doing something!
  reject("ERROR!!!");
});

p.then(
  res => {
    /* promise resolved */
    console.log(res);
  },
  err => {
    /* promise rejected */
    console.log(err);  // ERROR!!!
  }
);

reject함수에 담겼던 값이 then의 두번째 콜백함수에서 출력되는 것을 확인할 수 있습니다.

만약 성공적으로 처리한 경우만 다루고 싶다면, then에 첫번째 인자만 정의해주면 됩니다.

p.then(
  res => {
    /* promise resolved */
    console.log(res);
  }
);

생략도 가능합니다.

p.then(
    /* promise resolved */
    console.log
);

catch

catch는 에러를 처리해주는 부분입니다.
try-catch문을 생각하면 이해가 쉬울 것 같습니다.

let p = new Promise(function(resolve, reject) {
  // Doing something!
  reject("Error!!!");
});

p.then(
  res => {console.log(1,res)}
).then(
  res => {console.log(2,res)}
).catch(
  err => {console.log(err)}  //Error!!!
);

finally

finallyPromise가 성공했든 실패했든 마무리가 지어지면 실행되는 부분입니다.
try-catch-finally문을 생각할 수도 있는데 약간 다릅니다.

let p = new Promise(function(resolve, reject) {
  // Doing something!
  resolve(1);
});

p.then(
  res => {console.log(res);}  //1
).catch(
  err => {console.log(err);}
).finally(
  () => {console.log("finally!!")}  //finally!!
);

이렇게 보면 thencatch가 전부 끝난 후 finally가 실행되는 것처럼 보이지만,
finallyPromise의 프로세스가 마무리된 후 실행되는 부분이기 때문에 어느 위치든 Promise이후면 실행됩니다.

let p = new Promise(function(resolve, reject) {
  // Doing something!
  resolve(1);
});

p.finally(
  () => {console.log("finally!!")}  //finally!!
).then(
  res => {console.log(res);}  //1
)

위 예시처럼 then이 끝나야 finally가 실행되는게 아니라 Promise가 종료되고나서 finally가 실행되고, 결과값은 finally를 통과해서 then으로 넘어가게 됩니다.


댓글남기기