Skip to content

Instantly share code, notes, and snippets.

@singun
Last active March 17, 2016 02:37
Show Gist options
  • Save singun/1ac2393c1de8c16ee9eb to your computer and use it in GitHub Desktop.
Save singun/1ac2393c1de8c16ee9eb to your computer and use it in GitHub Desktop.
ES6 Promises in Depth - 1

Promises

  • Promises/A+ 스펙에 따르면, 이미 bluebired와 같은 라이브러리에서 구현되어 널리 사용됨
  • Promise는 트리 구조로 동작. p.then(handler), p.catch(handler)를 이용해서 branch를 추가
  • new Promise((resolve, reject) => { /* resolver */})를 이용해서 새로운 promises를 생성
    • resolve(value) callback은 value를 이용해서 promise를 수행
    • reject(reason) callback은 p를 거절
    • 비동기적으로 resolver와 reject를 호출 가능
  • Promises는 pending 상태에서 시작하고, fullfilled되거나 rejected되면 settled로 상태가 변경
  • Promises는 오직 한번만 settled 될 수 있으며, settled promise는 deeper branch를 막는 역할을 수행
  • promise는 체인의 형태로 여러번 사용 가능
  • .then 핸들러나 .catch 핸들러 중 하나만 실행 가능하며, 둘다 수행할 수는 없음
  • p.catch(fn).catch(fn)은 예상한 대로 동작하지 않을 수 있음
  • Promise.resolve(value)는 value를 사용하는 promise 객체를 생성
  • Promise.reject(reason)은 거절하는 promise를 생성
  • Promise.all(...promises)은 all() 메소드 내부에 있는 배열의 모든 요소들에서 수행되는 동작들이 실패하지 않았을때만 실행
  • Promise.race(...promises)은 하나라도 거부되면 나머지도 결과로 reject을 반환

ES6 Promises in Depth

What is a Promise?

a proxy for a value that will eventually become available

프록시 혹은 프록시 서버는 컴퓨터 네트워크에서 다른 서버 상의 자원을 찾는 클라이언트로부터 요청을 받아 중계하는 서버를 말한다. 미래의 어떤 특정 시점에 사용 가능한 값을 위한 객체라고 이해했다.

fetch('foo')

fetch 메소드는 Promise 객체를 반환한다. 아래의 예처럼 Promise의 static 메소드인 .then 체인을 만들어서 foo의 리소스를 가져왔을때 수행될 동작을 구현할 수 있다. fetch('foo').then(response => /* do something */)

Callbacks and Events

만약 fetch 가 콜백 함수를 사용한다면, 마지막 파라미터로 해당 콜백을 넘기고 수행이 끝날때 마다 실행된다. 일반적으로 비동기식 코드의 흐름(asynchronous code flow conventions)은 첫번째 파라미터로 error를 받아 들인다. 나머지 파라미터로는 실행 결과를 전달 받는다.

fetch('foo', (err, res) => {
  if (err) {
    // handle error
  }
  // handle response
})

해당 소스에서는 foo 리소스가 완전히 실행되기 전까지 콜백은 실행되지 않는다. 위와 같은 모델에서는 결과 값에 대해서 하나의 콜백만 등록할 수 있다.

응답을 처리하기 위해서 아래의 예처럼 _event-driven model_을 사용할 수도 있다. fetch에 의해서 리턴되는 객체를 on 이벤트에 연결한다. 일반적으로 data 이벤트는 성공적으로 수행되었을 경우에 호출되며, error 이벤트는 실패 한 경우에 실행된다.

fetch('foo')
  .on('error', err => {
    // handle error
  })
  .on('data', res => {
    // handle response
  })

이 경우에 예외 처리에 대한 이벤트 리스너가 구현되어 있지 않으면 error는 위험에 처하게 된다.

Gist of Promises

promises는 .on을 통해서 이벤트 리스터에 바인딩하지 않는다. fetch 메소드는 Promise객체를 리턴하는데, Promise 객체의 .catch.then을 이용해서 이벤트 리스너들을 바인딩 할 수 있다.

var p = fetch('foo')
p.then(res => {
  // handle response
})
p.catch(error => {
  // handle error
})

.then은 두번째 argument로 rejection을 등록할 수 있다.

fetch('foo')
  .then(
    res => {
      // handle response
    },
    err => {
      // handle error
    }
  )

.then(null, rejection).catch(rejection)과 동일하게 사용할 수 있다. 그리고 .then.catch항상 새로운 promise 객체를 반환한다. 항상 새로운 promise 객체가 생성되기 때문에 체이닝(chaining)이 가능하다. example source code

Promises in Time

Promises aren’t all that new. Like most things in computer science, the earliest mention of Promises can be traced all the way back to the late seventies. According to the Internet, they made their first appearance in JavaScript in 2007 – in a library called MochiKit. Then Dojo adopted it, and jQuery followed shortly after that.

Then the Promises/A+ specification came out from the CommonJS group (now famous for their CommonJS module specification). In its earliest incarnations, Node.js shipped with promises. Some time later, they were removed from core and everyone switched over to callbacks. Now, promises ship with the ES6 standard and V8 has already implemented them a while back.

The ES6 standard implements Promises/A+ natively. In the latest versions of Node.js you can use promises without any libraries. They’re also available on Chrome 32+, Firefox 29+, and Safari 7.1+.

Then, Again

아래의 소스 코드는 가장 간단한 형태의 promise 예제이다.

fetch('foo').then(res => {
  // handle response
})

위 코드에서 에러가 발생하면 어떻게 동작할까?

fetch('foo')
  .then(res => res.a.prop.that.does.not.exist)
  .catch(err => console.error(err.message))

.catch를 이용해서 에러를 잡아 낼 수 있다. 그렇다면 아래의 소스 코드는 어떤 결과를 출력할까?

fetch('foo')
  .then(res => res.a.prop.that.does.not.exist)
  .catch(err => console.error(err.message))
  .catch(err => console.error(err.message))

두번째 .catch는 첫번째 .catch에 의해 생성된 promise 객체에서 발생하는 error를 다룬다. 그렇다면 에러 메세지를 2번 출력하기 위해서는 어떻게 할까? 다음 소스 코드를 보자.

var p = fetch('foo').then(res => res.a.prop.that.does.not.exist)
p.catch(err => console.error(err.message))
p.catch(err => console.error(err.message))

조금 다른 예제를 보자. 해당 소스 코드에서는 에러에 대한 처리가, 첫번째 .catchrejection branch에 해당하는 두번째 catch에서 이루어 지고 있다.

fetch('foo')
  .then(res => res.a.prop.that.does.not.exist)
  .catch(err => { throw new Error(err.message) })
  .catch(err => console.error(err.message))

아래 코드 처럼 첫번째 .catch에서 아무것도 리턴하지 않는다면, 아무런 출력이 없다.

fetch('foo')
  .then(res => res.a.prop.that.does.not.exist)
  .catch(err => {})
  .catch(err => console.error(err.message))

promise들은 제멋대로(arbitrarily) 체인이 만들어 질 수 있는지 주시(observe) 해야 한다. 다시 말해서, promise chain에 있는 어떤 지점에 대한 참조를 저장할 수 있고 더 많은 promise들을 chain의 끝에 붙일 수 있다.

You can save a reference to any point in the promise chain.

var p1 = fetch('foo')
var p2 = p1.then(res => res.a.prop.that.does.not.exist)
var p3 = p2.catch(err => {})
var p4 = p3.catch(err => console.error(err.message))
  1. fetch새로운 p1 promise 객체를 리턴한다
  2. p1.then새로운 p2 promise 객체를 리턴한다
  3. p2.catch새로운 p3 promise 객체를 리턴한다
  4. p3.catch새로운 p4 promise 객체를 리턴한다
  5. p1이 _fulfilled_이 되어 settled 상태가 되면, p1.then이 실행된다
  6. After that p2, which is awaiting the pending result of p1.then is settled
  7. p2rejected 상태기 때문에, p2.then 브랜치 대신에 p2.catch가 수행된다.
  8. p3 promise는 p2.catch가 특정한 값을 생산하거나 에러를 일으키지 않기 때문에 fulfilled 상태가 된다.
  9. p3는 항상 성공이기 때문에, p3.catch은 절대 실행되지 않는다.

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment