Promise에 대해 (async, await)

·

3 min read

서론

let a = 1;
let b = 2;

위 코드에서 우리는 별다른 문제없이 값을 받아볼 수 있다는 것을 이해하고 있다. a가 1이고 b가 2이며 이를 더하면 3이라는 것을 인터프리터도, 나도 알고 있는 부분이다.

하지만 어떤 값인지 모르기 때문에 그 값을 외부에서 요청해야 할 때가 있다.

// Jquery
var userData = $.ajax(...);

위의 함수들은 javascript 개발자라면 둘중 하나는 무조건 알고 있을만한 것들이다. 이 함수들의 공통점은 어떠한 데이터를 가져오기 위해 기다려야 한다는 특징이 있다. 그러니까 언젠가는 데이터를 들고 와서 내게 값을 뿌려주겠지만, 그게 언제인지 모른다는 문제도 있다는 의미.

let waiting = fetch(...); // waiting의 값이 언제 올지 모름.

위의 코드에서 anythingToDo는 waiting을 순전히 기다려주어야만 할까? anythingToDo가 무언가를 하고 돌아왔을 때 waiting도 도착해있다면 얼마나 좋을까?

브라우저 입장에서 이를 온전히 기다려 주는 것은 크나큰 리스크가 있다. 우리 입장에서야 ajax요청 한번정도는 체감이 크지 않다는 것은 사실이지만 브라우저에서 ajax를 동기적인 방식으로 가져오게되면, 그러니까 waiting의 답이 오기까지 기다리는 방식을 사용하면 그때까지 브라우저에서는 메인 스레드가 멈춰버리니 브라우저와 사용자간의 의사소통이 차단되어 최악의 UX를 제공하는 결과를 낳게 된다.

이때 우리가 언젠가 올거라는 약속을 받고, 그에 대한 내용은 미리 만들어 두는 것이 어떨까? 해서 등장한 것이 바로 thenable이었다.

thenable

then이 있는 모든 객체를 말한다.

soAmIStillWaiting.then((response) => console.log(response)); // anythingToDo와는 별개로 나중에 답을 줌.

??? : 객체에 then이 있다고 thenable이라고 부르나?

{ then: () => {} } // thenable

정답이다.

const thenable = {
  then: () => {
    console.log('boom !')
  }
}

자세한 것은 후술하겠다.

Promise

드디어 본론으로 진입했다. 사실 서론이 너무 긴 경향이 있었는데, Promise는 그에 비해 지나치게 간단하다. 위처럼 무언가 나중에 응답을 받아야 할 때, 와서 then이라는 함수를 실행시키겠다는 약속 정도라고 일단 이해해두자.

여기서, then이라는 함수를 실행시킨다고 했는데 그럼 Promise도 thenable일까? 정답이다. then을 붙일 수 있다는 것은 thenable이라는 것이고 Promise는 thenable이기 때문이다.

하지만 모든 thenable이 Promise인 것은 아님.

thenable인데 Promise가 아닐 상황도 존재한다. 바로 Promise를 지원하지 않는 노답 브라우저에서는 Promise를 쓸 수 없어 thenable을 수동적으로 구현하기도 했다.

$.ajax(...).then(...);

Jquery가 대표적일 것이다. jquery의 ajax함수는 브라우저가 Promise를 지원하지 않을 때도 then의 기능을 하도록 만들어준다.

Promise에 대해서는 아주 잘 기술한 블로그가 상당히 많아서 (사실 MDN 글만 봐도 됨) 글을 쓰면서도 반신반의한 감이 있었는데, thenable에 대해서까지 상세하게 알려주는 이들은 없었던 것 같다. 그러니 Promise에 대해 기술하는 하단의 내용들은 읽지 않고 넘어가도 되지만, 간단하고도 모를 수 있는 thenable에 대한 위 내용은 숙지하도록 하자.

Promise의 상태

  • Pending 대기

  • Fulfilled 이행

  • Rejected 거부

굳이 외워야 할 필요가 없을 정도로 쉽다. 요청을 보냈지만 아직 응답을 받지 못한 상태를 pending, 또 다른 상태가 나머지 둘을 가리키는 settled인데 성공하면 fulfilled, 실패하면 rejected가 된다.

위의 내용을 이해하기가 다소 버거울 수 있기 때문에 코드로 보도록 하자.

promise()
  .then() // @return Promise<>
promise()
  .then()
  .then()
  .catch()
  .then()

Promise... thenable..?

아래 코드는

let hi = {};
hi.then = () => setTimeout(() => console.log('hihi'), 3000);

미리 상술한 $.ajax함수는 이 then을 수동적으로 만들어준다. 만들어주기만 해도 모던 자바스크립트 환경에서는 Promise로 인식한다. 놀랍지 않은가?

Promise의 기능

resolve

성공

reject

실패

all

모두 완료되었을 때까지 기다린다

race

여러개를 보내서 먼저 도착한걸 반환한다.

Async, Await

이 둘은 콜백지옥을 쓰지 않고도 Promise를 사용할 수 있게 해주는 놈들이다.

thenable(...)
.then(() =>
  thenable()
  .then(() =>
    thenable()
    .then())
);

글자들일 뿐이지만 마치 사탄이 지옥문을 열고 내게 손짓하는 형상같지 않은가? 아니라고 느껴진다면 저런 환경에서 작업을 해보지 않았던 것은 아닐까? 순수히 Promise만 사용하게 된다면 직면할 수 있는 상황이다. 하지만 async, await을 사용하게 된다면

await thenable(...)
await thenable(...)
await thenable(...)

이렇게나 아름답게 바꿀 수 있다..!

Await

await은 async이 붙은, Promise를 반환할 함수에서 사용할 수 있다. thenable 객체를 받는다.

const thenable = () => ({ then: (resolve, reject) => {} });

resolve, reject를 인수로 받는다.

Catch

await을 쓸때는 어떻게 에러를 핸들링할까?

try {
  await thenable();
} catch(e){
  console.log(e)
}

간-단