yngmanie 블로그

promise란? (2)

해당 포스트는 Master the JavaScript Interview: What is a Promise?을 번역하여 작성하였습니다. 잘못된 부분이 있다면 댓글 부탁드립니다.

이 글을 읽기 전에 promise란? (1) 을 먼저 보시는 것을 추천해 드립니다.

promise 체이닝

.then()은 항상 새로운 promise를 리턴하기때문에 어디에서 문제가 있고 에러가 나는지 정확하게 컨트롤하도록 promisefmf 묶을 수 있다. promise는 동기 코드의 try/catch를 모방할 수 있게 합니다.

동기 코드와 같이, 체이닝을 사용하면 연속적인 스퀀시가 생성됩니다. 즉 아래 코드와 같이 작성할 수 있습니다.

fetch(url)
  .then(process)
  .then(save)
  .catch(handleErrors);

fecth(), process(), save()가 promise를 반환한다고 가정하면 process()는 fetch() 실행이 완료될 때까지 기다리고 save()는 process() 실행이 완료될때까지 기다립니다. handleErrors()는 promise가 실행되는 과정에서 문제가 생기면 발행됩니다.

아래 다양한 rejection들이 있는 복잡한 promise가 있습니다.


wait(200)
// onFulfilled()가 새로운 promise x를 리턴할 수 있습니다.
.then(() => new Promise(res => res("foo")))

// 다음 promise는 상태를 x로 가정합니다.
.then(a => a)

// 위에 우리는 감싸여있지 않은 x를 리턴했습니다. 그래서 .then() fulfilled promise 값을 리턴합니다.
.then(b => conosle.log(b)) // "foo"

// null은 유효한 promise 값입니다.
.then(() => null)
.then((c => conosle.log(c)) // null

// 아직 에러가 발생하지 않았습니다.
.then(() => throw new Error("foo");)

// 대신에 아래와 같은 이류로 rejecte된 promise가 리턴됩니다.
.then(
  // 위로 에러때문에 아무런 로그도 나타나지 않습니다.
  d => console.log(`d: ${d}`),

  // 이제 에러를 핸들링할 수 있습니다.
  e => console.log(e) // [Error: foo]
)

// 이전 예외처리가 되면 계속 진행할 수 있습니다.
.then(f => console.log(`f: ${f}`)) //f: undefined

// 로그를 찾을 수 없습니다. e가 이미 핸들링되었기 때문에 이 핸들러는 더 이상 호출되지 않습니다.
.catch(e => console.log(e))
.then(() => {throw new Error("bar");})

// promise가 reject 됐을 때, 성공 핸들러는 취소됩니다.
// "bar"예외 때문에 fhrmrk djqttmqslek.
.then(g => console.log(`g: ${g}`))
.catch(h => console.log(h)) // [Error: bar]

에러 핸들링

promise는 성공과 에러 모두 핸들링할 수 있으며 아래의 코드처럼 일반적으로 사용합니다.

save().then(handleSuccess, handleError);

만약 handleSuccess()가 에러를 발생시키면 어떻게될까요? promise는 .then을 통해 reject 되지만 rejection을 catch할 수 있는 방법이 없습니다. 즉 앱이 에러를 무시하게 됩니다.

이러한 이류로 사람들은 위에 코드를 안티 패턴으로 취급하고 아래와 같이 코드를 작성하는걸 추천합니다.

save()
  .then(handleSuccess)
  .catch(handleError);

이 차이는 미묘하지만 중요합니다. 예를 들어 save()에서 발생한 오류는 잡을 수 있지만 handleSuccess()에서 발생한 오류는 무시됩니다. promise without catch() error

.catch()는 save()와 handleSuccess()에서 모두 에러를 잡을 수 있습니다. promise with catch() error

물론 save() 에러는 네트워크에러일 수 있지만 handleSuccess() 에러는 개발자가 잘못으로 발생할 가능성이 큽니다. 두 가지 에러를 구분해서 확인하고 싶다면 아래와 같이 코드를 작성하면 됩니다.

save()
  .then(handleSuccess, handleNetworkError)
  .catch(handleProgrammerError);

무엇을 선호하든 간에 promise 체인에 마지막은 .catch()를 사용하길 추천합니다.

결론

promise는 모던 ajax 요청에서 사용되는 WHATWG Fetch표준과 비동기 코드를 동기로 변경하는데 필요한 비동기 함수 표준과 같이 javascript에서 큰 역할을 하고 있습니다.

비동기함수는 현재 글을 작성하고 있을 때는 3개의 스테이지로 구분되어있지만 앞으로 javascript 프로그래밍에서 가장 인기 있고 보편적으로 사용될 것입니다. 가까운 미래에 javascript 개발자에게 있어서 promise를 공부하는 것을 중요한 일이 될 겁니다.

예를 들어, 리덕스를 사용하고 있다면 필자는 redux-saga를 참고하는 것을 추천드립니다. redux-saga는 redux에서 비동기함수를 사용하는데서 발생하는 사이드이펙트를 매니지하는 라이브러리입니다.

필자는 이미 promise에 대해 알고 있던 사람들도 이 글을 통해 좀 더 깊게 promise에 대해서 이해했으면 좋겠고 좀 더 올바르게 사용하는 것을 바란다고 합니다.