[JS] 프로미스(Promise) (1)

jYur·2022년 12월 6일
0

Callback

Promise 이전엔 콜백(callback) 지옥이란 것이 존재했어요.

최초 함수 호출 시 콜백 함수를 함께 전달하고

전달된 콜백 함수 안에서 함수 호출 시 다른 콜백 함수를 전달하고,

마찬가지로 그 다른 콜백 함수 안에서 함수 호출 시에도 또 다른 콜백 함수를 전달하고

...

코드로는 대충 이런 식

fetch("some-valid-url", (response) => {
  parseToJson(response, (json) => {
      filterData(json, (filteredData) => {
        console.log(filteredData);
      });
  });
});

Promise 덕분에 아래와 같이 indent depth가 증가하지 않게 작성할 수 있다.

fetch("some-valid-url")
  .then(parseToJson)
  .then(filterData)
  .then(console.log);

Promise

내부에서 argument에 따라 계산하고 그 결과를 반환하는 명쾌한 행위와는 다르게,
외부(예: API, DB 등등)에 요청(혹은 메시지)를 보내고 응답을 받는 행위는 좀 복잡해요.
외부의 사정에 따라 응답이 늦게 올 수도 있고 아예 못 받을 수도 있거든요. 실제로 어떻게 될지는.. 기다려 봐야 아는 거죠.
그런데 기다리는 동안 다른 일을 못하면 손해잖아요? 요청은 보내 놨으니 일단 다음 작업으로 넘어가고 싶어요.
하지만 그냥 넘어가면 잊어버릴 테니 잊지 않도록 할 장치가 필요한데, 그게 Promise랍니다.

외부의 응답을 기다려야 하는 함수들은 응답을 받지 못했더라도 기다리지 않고 일단 Promise를 반환하도록 해요. 그럼 그 함수를 호출하자마자 Promise가 반환되면서 호출된 함수 실행이 끝나니까 다음 작업으로 넘어갈 수 있겠죠. 넘어가기 전에, 응답을 받았을 경우(또는 응답 받는 데 실패했을 경우)에 실행할 작업들을 반환된 Promise.then() 메서드를 이용해서 미리 알려주고요.

( 참고로, '내가 할 일이 많아서 오래 걸리는 경우'와 '내가 할 일은 끝냈는데 다른 사람이 이어서 처리하는 걸 기다리느라 오래 걸리는 경우'는 달라요. Promise는 둘 중 뒤의 경우에 사용되는 거고요. )

코드로 보는 Promise

1.

간단히 Promise를 하나 만들어 보자.

  • new Promise()에는 함수 하나를 전달해야 한다. 그래서 아무 것도 하지 않는 익명 함수 하나(() => {})를 전달했다.
  • PromiseState에 보이는 것은 Promise의 상태다.
    보이는 것처럼 만든 직후의 상태는 pending(기다리고 있는 상태).
  • PromiseResult에 보이는 것은 실제 우리가 필요한 데이터다.
    상태가 pending일 때는 result가 undefined이고
    상태가 fulfilled(기다림 후 원하는 걸 얻은 상태)가 되면 실제 데이터로 바뀔 것이다.
    상태가 rejected(기다림 후 원하는 걸 얻지 못한 상태)가 될 수도 있다.

2. Promise를 resolve하다

  • 아까처럼, new Promise()에 전달한 것은 단지 함수 하나다.
  • 그 하나의 함수가 다시,
    한 개의 파라미터(이름: resolve)를 통해서
    한 개의 함수를 전달받아 (전달받는 함수는 "Promise를 resolve하는 함수")
    그 함수를 호출할 뿐이다.
    • "Promise를 resolve하는 함수"를 호출할 때 값을 전달하면 그 값이 Promise의 result가 된다. (위 코드에서 777)
    • Promise를 resolve하면 state가 fulfilled로 바뀐다.

아래는 이에 대한 공식 설명이다.

  • new Promise()에 전달하는 함수는 Promise를 초기화하는 용도란다.
  • 아까는 한 개의 파라미터(이름: resolve)로 "resolve하는 함수"만 받았지만 사실 파라미터를 하나 더 추가해서 "reject하는 함수"도 같이 받을 수 있다.
  • "Promise를 resolve하는 함수"를 호출할 때
    아까처럼(resolve(777);) 값을 전달할 수 있지만
    값 대신 또 다른 Promise를 전달할 수도 있다.

    여기서 주의!
    조금 전에 "Promise를 resolve하는 함수" 호출 시 전달된 것이 값일 때는 그 값이 바로 'resolve되는 Promise의 result'가 되었다.
    그러나, 전달된 것이 Promise인 경우 마찬가지로 그 Promise가 바로 result가 되는 것은 아니다.
    다시 말해, 실제 result가 되는 것은 '전달된 Promise가 resolve됐을 때의 result'다.

3. .then()

HTML 파일을 만들고 아래처럼 스크립트 코드를 넣은 후 실행하면 브라우저 개발자 도구 콘솔에 뉴스 기사들이 담긴 배열이 출력될 것이다.

<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body></body>
  <script>
    function responseToJson(response) { // HTTP 응답을 받아 JSON 형식의 데이터를 반환하는 함수
      return response.json();
    }

    function printJson(json) { // 데이터를 받아 콘솔에 출력하는 함수
      console.log(json);
    }

    fetch("https://api.hnpwa.com/v0/news/1.json") // 1
      .then(responseToJson)                       // 2
      .then(printJson);                           // 3
  </script>
</html>
  1. fetch() 함수를 호출하여 외부 API에 데이터 요청.
  2. .then()에 함수 responseToJson 전달.
    1번에서 반환된 Promise의 상태가 fulfilled가 되면 responseToJson 함수가 호출됨.
  3. .then()에 함수 printJson 전달.
    2번에서 반환된 Promise의 상태가 fulfilled가 되면 printJson 함수가 호출됨.
  • .then() 또한 Promise를 반환한다. 그래서 .then() 다음에 또 .then()을 사용할 수 있는 것.
  • .then()에 전달되는 함수는 값을 반환해도 되고 Promise를 반환해도 된다.
    • 값을 반환하면 '다음 .then()에 전달되는 함수'의 첫 번째 파라미터 인자로 그 값이 전달된다.
    • Promise를 반환하는 경우, '다음 .then()에 전달되는 함수'의 첫 번째 파라미터 인자로 전달되는 것은 반환된 Promise 자체가 아니라, 반환된 Promise가 resolve됐을 때의 result다.
new Promise((resolve) => {
  resolve(777);
})
  .then((num) => {
  console.log(`first num: ${num}`);
  return num;
})
  .then((num) => {
  console.log(`second num: ${num}`);
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(num);
    }, 2_000);
  });
})
  .then((num) => {
  console.log(`third num: ${num}`);
});
출력:
  first num: 777
  second num: 777
  
  (2초 후..)
  
  third num: 1000

0개의 댓글