logo

프로미스

Chapter 25

46 조회

0 추천

1,012 단어

6분 예상

2024. 08. 20. 게시

2025. 02. 26. 수정

luasenvy 작성

CC BY-NC-SA 4.0

Promise

프로미스는 후에 실행할 작업을 등록할 수 있다. 콜백함수와 동일한 구조를 가지고 있다.1 그도 그럴것이 콜백헬을 대처하기 위해 제안된 기능이다. 이해를 위해서는 콜백에 대해 먼저 짚고 넘어가야 한다.

콜백함수

const asyncFn = (callback) => {
  // 5초 후 원하는 작업이 완료되면 콜백함수 호출
  setTimeout(() => callback(10), 5 * 1000);
}

// 비동기 함수를 호출하면서 완료 이후에 작동할 함수를 미리 제공
asyncFn((result) => {
  console.info(`Async Function Has Returned. ${result}`);
})

비동기 동작의 시점 문제를 해결하기 위한 방법으로 작업이 완료된 이후에 동작할 함수를 미리 제공하는 방법이다.

콜백헬

asyncFn(() => {
  asyncFn(() => {
    asyncFn(()=> {
      asyncFn(()=> {
        // ...
      })
    })
  })
})

비동기 작업을 합리적으로 처리할 수 있는 방안이지만 비동기 작업 이후에 다시 비동기 작업을 해야하는 경우 등 콜백함수를 활용하는 구간이 많아지면 가독성이 아주 떨어져 유지보수가 힘들어진다.

위 예시는 원리만 표현해서 그렇지 여러 작업들이 끼어들고 에러처리를 하는 등 점점 그 모양새가 맛이 가기 시작하면 처다보기도 싫어진다. 프로세스 순서를 쫓아가는건 기본이고 내부에서 다루는 데이터 구조가 트리처럼 조금이라도 복잡하다면 손을 놓기 십상이다. 이름에서 볼 수 있듯이 말 그대로 지옥을 경험할 수 있다.

어디 그뿐이겠는가? 스코프에 대한 이해도 필요하고 클로저나 호이스팅 같은 현상도 고려해야 한다. 사실상 유지보수는 못 한다고 봐야하며 몇 개월 후에 그냥 싹다 뒤집어 엎는 수순을 밟는게 일반적이다.

const asyncFn = () => new Promise((resolve, reject) => {
  setTimeout(() => resolve(Math.trunc((Math.random() * 500))), Math.trunc((Math.random() * 10000)))
});

asyncFn()
  .then((res) => asyncFn())
  .then((res) => asyncFn())
  .then((res) => asyncFn())
  .then((res) => asyncFn())
  .then((res) => asyncFn())
  .catch(err => console.error(err))
  .finally(() => console.info("run finally"))

callback을 사용하던 함수의 몸체를 Promise의 인자로 만들어 넘겨주고 callback 함수를 호출하는 대신 Promise가 지원하는 resolve() 함수로 전달하도록 변경하였다. 함수의 반환값은 new Promise()를 통해 생성된 Promise 인스턴스가 되도록 변경했다.

Promise를 사용하면 기존의 콜백헬을 직렬화 할 수 있다. callback 함수를 호출하는 대신 Promise가 지원해주는 resolve 함수로 반환값을 리턴하면 .then() 콜백에서 사용할 수 있다. 중간에 에러가 발생하여 throw 되거나 reject() 함수로 에러를 전달하면 .then()의 유무와 개수에 관게없이 .catch()로 작업이 전달된다. finally()는 오류 유무와 관계없이 마지막에 한 번 실행된다. .then() 함수에서 다시 Promise 인스턴스를 반환하면 비동기처리가 완료되고 난 후 다시 .then()으로 처리할 수도 있다.

asyncFn()
  .then((res) => {
    asyncFn()
      .then((res) => {
        asyncFn()
          .then( /* ... */ )
      })
  })

좀 웃긴 예제이긴 한데 오류없이 동작한다. 실무를 하다보면 프로미스를 활용하면서 콜백을 통해 문법적 우회를 시도하는 일이 생기고는 한다. 그러면 위 같은 어처구니 없는 경우도 분명 발생한다. 이럴때는 이렇게 하면 되네? 가 아니라 뭔가 처음부터 잘못되었나? 를 고려해보자. 콜백헬을 굳이 프로미스까지 이어올 필요는 없다. Promise를 올바르게 사용하도록 하자.

Footnotes

  1. nodejs의 내장객체인 util 모듈을 보면 이런 콜백함수를 프로미스로 변환해주는 promisify와 같은 함수가 제공된다.