Promise 문제 하나 풀고 가!

­가은·2023년 10월 26일
14
post-thumbnail

부스트캠프에서 인터미션 전에 부캠 골든벨 문항을 출제하라는 미션을 내주었다.
나는 Promise 부분 문제를 만들게 되었다.
기본적이지만, Promise에 대해 잘 모른다면 헷갈릴 만한 내용들로 문항을 구성해보았다.


🍞 다음 중 Promise에 대해 옳지 않은 문항을 모두 고르시오.

  1. 비동기 처리는 Promise 생성자 함수가 인수로 전달받은 콜백 함수 내부에서 수행된다.
  2. Promise는 한 번 fulfilled 상태가 되면 다시는 rejected 상태로 변화할 수 없다.
  3. then 메서드는 언제나 프로미스를 반환한다.
  4. catch 메서드의 콜백 함수는 Promise가 rejected 상태일 때만 호출된다.
  5. finally 메서드의 콜백 함수는 Promise가 성공했을 시에 호출된다.





정답: 5번

이 글은 Promise에 대해 하나부터 열까지 모두 설명하지는 않는다.
하지만 문제에서 기본적인 것들을 묻고 있는 만큼, 기본 개념 위주로 글을 작성했다.
하나라도 헷갈리는 부분이 있다면 해설을 읽어보는 것을 추천한다.




🍞 1번 해설

비동기 처리는 Promise 생성자 함수가 인수로 전달받은 콜백 함수 내부에서 수행된다.

⭕️

Promise 생성자 함수를 new 연산자와 함께 호출하면 Promise 객체를 생성한다.
여기서 Promise 생성자 함수는, 비동기 처리를 수행할 콜백 함수를 인수로 전달받는다.
콜백 함수는 resolve와 reject 함수를 인수로 전달받는다.

// 프로미스 생성
const promise = new Promise((resolve, reject) => {
  // Promise 함수의 콜백 함수 내부에서 비동기 처리를 수행
  
  if (/* 비동기 처리 성공 */) {
    resolve('result');
  } 
                            
  else { /* 비동기 처리 실패 */
    reject('failure reason');
  }
  
});

예시가 너무 딱딱하니까 조금 친근하게 살펴보자.
실제로는 저런 식으로 사용하지 않지만.. 이해를 돕기 위해 쉽게 작성해보았다.
도라에몽이 물건을 사러 나간 상황이다.

const buyItem = (item, action) => {
  
  return new Promise((resolve, reject) => {
    if (action === 'success') { // 비동기 처리 성공
      resolve(`${item}을/를 성공적으로 샀어요!😋`);
    } 
    
    else { // 비동기 처리 실패
      reject(new Error(`도라에몽: ${item}을/를 살 수 없었어요...🥹`));
    }
  });
};

const buySuccess = buyItem('책', 'success');
const buyFailure = buyItem('컴퓨터', 'failure');

이렇게 인수로 전달받은 콜백 함수 내부에서 비동기 처리를 수행하는 것을 볼 수 있다.
비동기 처리가 성공하면 콜백 함수의 인수로 전달받은 resolve 함수를 호출하고,
비동기 처리에 실패하면 reject 함수를 호출한다.


🍞 2번 해설

Promise는 한 번 fulfilled 상태가 되면 다시는 rejected 상태로 변화할 수 없다.

⭕️

혹시 fulfilled와 rejected가 뭔지 모를 사람들을 위해 간단히 설명하고 넘어가겠다.

출처: <자바스크립트 딥다이브> ch.45 프로미스

Promise는 현재 비동기 처리가 어떻게 진행되고 있는지 나타내는 상태 정보를 갖는다.

  • pending: 비동기 처리가 아직 수행되지 않은 상태
  • fulfilled: 비동기 처리가 수행된 상태 (성공)
  • rejected: 비동기 처리가 수행된 상태 (실패)

이 때 비동기 처리가 성공했을 때 resolve 함수를 호출하고 fulfilled 상태가 되고, 실패했을 때 reject 함수를 호출하고 rejected 상태가 되는 것이다.

이 때 비동기 처리가 수행된 상태, 즉 fulfilled 혹은 rejected 상태가 된 것을 settled 상태라고 한다.
비동기 처리가 수행되기 전은 pending, 수행되고 나서는 settled가 되는 것이다.

이 때 Promise는 당연히 pending 상태에서 settled 상태로 변화할 수 있다.
하지만 일단 settled 상태가 되었다면 다시는 다른 상태로 변화할 수 없다.
fulfilled에서 rejected로 변화하거나, rejected에서 다시 pending으로 돌아갈 수 없다는 뜻이다.


🍞 3번 해설

then 메서드는 언제나 프로미스를 반환한다.

⭕️

then 메서드에 대해 먼저 알아보자.
then은 Promise의 후속 처리 메서드 중 하나이다.

Promise가 fulfilled 상태가 되면 Promise의 처리 결과를 가지고 무언가를 해야 하고, Promise가 rejected 상태가 되어도 에러 처리를 해야 한다.

이를 위해 Promise는 후속 처리 메서드를 제공한다.
후속 처리 메서드에는 then, catch, finally가 있다.

Promise의 비동기 처리 상태가 변화하면, 즉 pending 상태가 settled 상태 (fulfilled 혹은 rejected 상태) 로 변화하면,
후속 처리 메서드에 인수로 전달한 콜백 함수가 선택적으로 호출된다.
여기서 후속 처리 메서드의 콜백 함수에는 Promise의 처리 결과가 인수로 전달된다.

위의 도라에몽 예시를 활용해 후속 처리 메서드 사용 예시를 보자.

const buyItem = (item, action) => {
  
  return new Promise((resolve, reject) => {
    if (action === 'success') { // 비동기 처리 성공
      resolve(`${item}을/를 성공적으로 샀어요!😋`);
    } 
    
    else { // 비동기 처리 실패
      reject(new Error(`도라에몽: ${item}을/를 살 수 없었어요...🥹`));
    }
  });
};

const buySuccess = buyItem('책', 'success');
const buyFailure = buyItem('컴퓨터', 'failure');

// 후속 처리 메서드 사용
buySuccess
  .then(result => { // fulfilled 상태 (비동기 처리 성공)
    console.log(`도라에몽: 약속을 지켰어요! 결과: ${result}`);
  })
  .catch(error => { // rejected 상태 (비동기 처리 실패)
    console.error(`도라에몽: 약속을 지키지 못했어요ㅠㅜ 오류: ${error.message}`);
  })
  .finally(() => { // fulfilled이든 rejected이든 무조건 한 번 호출됨
    console.log(`도라에몽: 사왔든 사오지 않았든 일단 도라야끼를 주세요!`);
  });

일단은 이해가 잘 안될 수 있다.
아래 내용들을 천천히 읽어보도록 하자.

모든 후속 처리 메서드는 Promise를 반환한다.
그러니 then 메서드가 언제나 Promise를 반환하는 것은 당연한 말이고, catch와 finally도 Promise를 반환한다.
만약 메서드의 콜백 함수가 Promise가 아닌 값을 반환해도, 그 값을 암묵적으로 resolve 또는 reject하여 Promise를 생성해 반환한다.

답을 알았지만, then에 대해서 조금만 더 알아보자.

then 메서드는 두 개의 콜백함수를 인수로 받는다.

첫 번째 콜백 함수는 비동기 처리에 성공했을 때 호출된다.
콜백 함수는 Promise의 비동기 처리 결과를 인수로 전달받는다.

두 번째 콜백 함수는 비동기 처리에 실패했을 때 호출된다.
콜백 함수는 Promise의 에러를 인수로 전달받는다.

아래와 같이 사용할 수 있다.

buySuccess
  .then(
  	result => { // fulfilled 상태 (비동기 처리 성공)
      console.log(`도라에몽: 약속을 지켰어요! 결과: ${result}`);
    },
    error => { // rejected 상태 (비동기 처리 실패)
      console.log( `도라에몽: 약속을 지키지 못했어요ㅠㅜ 오류: ${error.message}`);
    }
  );

하지만 에러처리는 then보다 catch를 사용하여 하는 것을 더 권장한다.
이에 관해서는 밑에서 이어 설명하겠다.


🍞 4번 해설

catch 메서드의 콜백 함수는 Promise가 rejected 상태일 때만 호출된다.

⭕️

비동기 처리에서 발생한 에러를 catch를 통해 처리할 수 있다.
catch 메서드의 콜백 함수는 Promise가 rejected 상태일 때만 호출된다.

아래와 같이 사용할 수 있다.
실제로는 buySuccess.then(...).catch(...) 처럼 쓰일 것이다.

buySuccess
  .catch(error => { // rejected 상태 (비동기 처리 실패)
    console.error(`도라에몽: 약속을 지키지 못했어요ㅠㅜ 오류: ${error.message}`);
  })

이는 then을 아래와 같이 사용했을 때와도 같다.

buySuccess
  .then(undefined, error => { // rejected 상태 (비동기 처리 실패)
    console.error(`도라에몽: 약속을 지키지 못했어요ㅠㅜ 오류: ${error.message}`);
  })

그렇다면 에러 처리 시 then과 catch 중 어떤 것을 사용해야 할까?

답은 catch이다.

then 메서드를 사용하면, 두 번째 콜백 함수가 첫 번째 콜백 함수에서 발생한 에러를 캐치하지 못한다.
또 코드가 복잡해져 가독성도 좋지 않다.

catch 메서드를 모든 then 메서드를 호출한 이후에 사용한다면, 비동기 처리에서 발생한 에러뿐 아니라 then 메서드 내부에서 발생한 에러까지 모두 잡아낼 수 있다.

buySuccess
  .then(result => console.doraemon(result))
  .catch(error => { // rejected 상태 (비동기 처리 실패)
    console.error(`도라에몽: 약속을 지키지 못했어요ㅠㅜ 오류: ${error.message}`); 
    // TypeError: console.doraemon is not a function
  })

🍞 5번 해설

finally 메서드의 콜백 함수는 Promise가 성공했을 시에 호출된다.

finally 메서드의 콜백 함수는 Promise의 성공 또는 실패와 상관없이 무조건 한 번 호출된다.
그래서 Promise의 성공, 실패 여부와 상관없이 공통적으로 수행해야 할 내용이 있을 때 쓸 수 있다.

buySuccess
  .then(result => { // fulfilled 상태 (비동기 처리 성공)
    console.log(`도라에몽: 약속을 지켰어요! 결과: ${result}`);
  })
  .catch(error => { // rejected 상태 (비동기 처리 실패)
    console.error(`도라에몽: 약속을 지키지 못했어요ㅠㅜ 오류: ${error.message}`);
  })
  .finally(() => { // 성공실패 여부와 상관없이 호출됨
    console.log(`도라에몽: 사왔든 사오지 않았든 일단 도라야끼를 주세요!`);
  });

이렇게 이야기하면 언제 쓰는 건지 감이 잘 안올 수도 있다.
한가지 예시로 로딩 상태를 감지할 때 사용할 수 있다.
데이터를 정상적으로 불러왔든, 에러를 뱉어냈든 일단 데이터가 처리되었다면 finally를 이용해 로딩 상태를 숨기는 것이다.



🍞 요약

  • 비동기 처리는 Promise 생성자 함수가 인수로 전달받은 콜백 함수 내부에서 수행된다.
  • Promise는 한 번 settled 상태가 되면 다시는 다른 상태로 변화할 수 없다.
  • then, catch, finally와 같은 후속 처리 메서드는 언제나 프로미스를 반환한다.
  • catch 메서드의 콜백 함수는 Promise가 rejected 상태일 때만 호출된다.
  • finally 메서드의 콜백 함수는 Promise의 성공, 실패 여부와 상관없이 항상 한 번 호출된다.

참고 자료

  • <자바스크립트 딥다이브> ch45. 프로미스

0개의 댓글