※ 이 글은 엄초딩(DJAWL_2)님의 블로그에서 가져온 글입니다.


jQuery에서의 Deferred와 Promise

 

원문 : http://www.bitstorm.org/weblog/2012-1/Deferred_and_promise_in_jQuery.html

 

 

Deferred와 Promise는 jQuery 1.5 이후 버전에서부터 포함된 기능으로 Ajax 같은 비동기 함수를 핸들링하기 위한 도구이다.

 

iPod이나 XBox, Facebook이 없던 시절을 떠올려 보자. 마우스클릭을 알아내기 위해 우리는 다음과 같이 했다. 

(왜 필자는 굳이 iPod이나 XBox를 예로 들었는지 잘 모르겠다)

 

element.onclick = someFunction;

 

하지만 위의 방법으로는 이 클릭을 코드 내의 다른 부분에서 추가로 listen 할 수가 없다는 문제가 있다. 한 개의 함수만 할당이 가능하다. 

 

우리는 이 문제를 addEventListener DOM 함수로 해결했다. 이를 통해 원하는 만큼 많은 리스너를 등록할 수 있다. 

 

하지만 이제 Ajax를 호출할 때도 이와 비슷한 문제를 겪는데 Ajax 역시 한 개의 callback 함수만을 지원하기 때문이다. (이벤트는 아니지만 대신 콜백함수로) 

jQuery의 $.ajax() 뿐 아니라 XMLHttpRequest 객체를 사용하는 다른 함수도 마찬가지이다.

 

 

Promise

 

jQuery 1.5 이전에는 $.ajax()를 이렇게 했다.

$.ajax({
  url: "/myServerScript",
  success: mySuccessFunction,
  error: myErrorFunction
});

$.ajax()는 jQuery의 XMLHttpRequest 객체를 리턴한다.

 

1.5 버전부터는 XMLHttpRequest 가 아니라 CommonJS Promise/A 인터페이스를 따르는 객체를 리턴하는데 CommonJS는 공통적이고 독립적인 인터페이스를 위한 조치라고 보면 된다. Promise/A는 그 인터페이스 중 하나이다. 이것은 jQuery에 의존적이지 않기 때문에 가령 Node.js 같은 것들을 사용할 때도 동일한 인터페이스를 계속 사용할 수 있는 장점이 있다.

 

Promise로 콜백을 등록하는 방법은 아래와 같다. 

var promise = $.ajax({
  url: "/myServerScript"
});
 
promise.done(mySuccessFunction);
promise.fail(myErrorFunction);

 

then() 함수를 사용하면 done()과 fain() 함수의 기능을 하나로 합칠 수도 있다. 아래 코드는 위와 동일하다.

var promise = $.ajax({
  url: "/myServerScript"
});
 
promise.then(mySuccessFunction, myErrorFunction);

 

그렇다면 이게 뭐가 좋다는 걸까. promise의 장점을 보자.

 

1) done()과 fail() 함수를 다른 콜백으로 여러번 호출할 수 있다. 애니메이션을 멈추는 콜백과 Ajax를 통해서 받은 데이터를 보여주는 콜백이 있다면 다음과 같이 코딩할 수 있다.

var promise = $.ajax({
  url: "/myServerScript"
});
 
promise.done(myStopAnimationFunction);
promise.done(myOtherAjaxFunction);
promise.done(myShowInfoFunction);
promise.fail(myErrorFunction);

 

2) Ajax 수행이 끝난 후에도 done()과 fail() 함수를 콜할 수 있고 이때 콜백이 바로 실행된다. 변수들이 서로 다른 상태가 되는 혼란스런 상황을 피할 수 있다. Ajax 콜이 끝나면 결과는 success 상태이거나 fail 상태이며 이 상태가 변하는 일은 없다. (이 말은 사실 잘 이해가 안 된다)

 

3) promise들을 서로 합칠 수 있다. 동시에 두 개의 Ajax 를 콜하고 이 두 개가 모두 성공적으로 종료한 후에 무언가를 실행해야 할 때가 있다. 이런 경우를 위해 아래와 같은 $.when() 함수가 있다.

var promise1 = $.ajax("/myServerScript1");
var promise2 = $.ajax("/myServerScript2");
 
$.when(promise1, promise2).done(function(xhrObject1, xhrObject2) {
  // Handle both XHR objects
});

콜백함수는 인자로 Ajax의 결과값을 받는 것은 아니고 XHR 객체를 받는다. 이는 사실 Ajax request를 위해 필요한 조치이며 $.when을 (Ajax가 아닌) 다른 비동기 코드에서 사용하는 건 좀 더 어렵긴 하다.

 

4) jQuery 1.8부터는 then() 함수를 통해 chain을 연결할 수 있다. 아래 코드에서 첫번째 promise1이 수행되고 성공적으로 resolve (결과가 success로 종료) 되면 그 뒤에 getStuff()가 수행된다. 그리고 나서 promise를 리턴하고 이것도 성공적으로 resolve 되면 그 뒤에 myServerScripte2Data를 인자로 하는 함수가 호출되는 것이다. (1.8 이전에는 queue() 함수가 이와 비슷한 기능을 했다 - jQuery의 queue() 사용하기)

var promise1 = $.ajax("/myServerScript1");
 
function getStuff() {
    return $.ajax("/myServerScript2");
}
 
promise1.then(getStuff).then(function(myServerScript2Data){
  // Both promises are resolved
});

모든 콜백은 이전 비동기 함수의 결과를 받는다. Ajax의 경우에는 리턴 데이터를 받는다.

(즉 위에서 promise1의 리턴 데이터를 getStuff이 받고, getStuff의 리턴 데이터를 그 뒤 익명함수가 받는다)

 

 

Deferred

 

Deferred는 무엇이며 Promise와는 어떻게 다를까. 

위에서 본 것처럼 Promise는 비동기함수가 리턴한 객체인데 이런 비동기함수를 내가 만들어 사용한다면 이때 Deferred를 사용하게 되는 것이다. 

(deferred의 사전적인 의미는 '연기된, 거치된, 징병 유예 중인'인데 전혀 의미가 와 닿지 않는다. 한글책에서는 어떻게 번역하고 있는지 모르겠다)

 

deferred 객체는 promise 객체와 같은 일을 할 수 있으며 추가적으로 2개의 함수를 더 갖는다. done()과 fail()을 trigger하기 위한 함수이다.

 

resolve() 함수는 done()에 등록된 함수를 실행하기 위한 함수이다. 결과가 성공일 때 호출한다.

reject() 함수는 결과가 실패일 때 fail()에 등록된 함수를 실행하기 위해 호출한다.

 

이 2개의 함수에 파라메터를 주면 done()과 fail()에 각각 전달된다. 

 

promise 객체에는 resolve()와 reject()가 없다. promise를 다른 스크립트로 전달할 수가 있는데 이런 상황에서는 promise를 resolve 하거나 reject 할 이유가 없기 때문이다. (리턴한다는 말인 듯)

 

* 추가

Promise에는 resolve()와 reject()가 없고 대신 isRejected()와 isResolved()가 있다. 이 함수들을 통해 deferred 객체의 상태를 체크만 할 수 있다.

promise 객체에 resolve()와 reject()가 없는 것은 실제 비동기함수가 수행을 완료하지 않았는데도 promise의 resolve나 reject를 통해 콜백이 미리 수행돼 버리는 상황을 막기 위해서이다.

(아래 예제를 보면 deferred.promise()가 먼저 리턴된 후에 deferred.resolve()가 수행되어 비동기함수가 종료되게 되는데, 이 resolve()가 수행되기 전인 리턴 직후에 promise.resolve()가 수행되는 비정상적인 상황이 만들어지면 안 되는 것이다)

 

아래는 deferred 코딩 예제이다. html 내용은 id가 reuslt인 빈 div 뿐이다.

(직접 실행해 보라고 jsFiddle 로 올린다)

 

 

 

wait() 함수는 promise를 리턴한다. 2초 후에 resolve 되도록 setTimeout()을 설정하고 있는데 여기서 비동기 처리를 위해 setTimeout() 말고 다른 함수를 사용해도 무방하다. 이를테면 애니메이션이라든가 Web Worker 같은 것들. 

또한 wait() 안에서 deferred 객체를 사용하지만 리턴은 promise로 하고 있다.

 

(wait()의 코드를 보면 setTimeout()을 호출하고 나서 return을 하는데 이게 2초를 기다렸다가 다음 코드를 이어서 처리하는 게 아니다. return이 먼저 수행되고 그로부터 2초 있다가 deferred.resolve()가 수행되는 것이다. javascript의 setTimeout()은 java나 여타 언어와는 다르게 쓰레드로 처리되지 않기 때문에 setTimeout()을 만나도 2초를 기다리는 것이 아니라 queue에 쌓아두었다가 현재 수행중인 코드가 다 종료되면 비로소 queue에서 순서대로 꺼내 실행할 수 있게 되는 것이다)

 

 

Criticism (비판적인 시각)

 

Domenic Denicola 라는 분이 You’re Missing the Point of Promises 라는 글에서 Promise/A 를 구현한 jQuery의 promise에 대해 비판하고 있다. (나도 아직 안 읽어봤다)

 

가령 다음과 같은 코드가 비판 대상이다.

promise.then(fn1).then(fn2).then(fn3, fail);
fn1에서 에러가 발생하면 fail() 함수가 호출되어야 한다. 이는 비동기 함수에서의 에러핸들링을 위한 nice한 방법이다. 그러나 jQuery에서는 실행되지 않는다. 저자는 이 문제를 jQuery의 중대한 결함으로 보고 있다.
 
"pure" Promise/A를 보려면 몇 개의 javascript 라이브러리가 있다. Kristopher Kowal의 Q 라는 라이브러리를 추천한다. 

 

 

마치며

 

더 자세한 내용은 jQuery deferred 공식문서 를 보기 바란다.

 

 

원문 : http://www.bitstorm.org/weblog/2012-1/Deferred_and_promise_in_jQuery.html

 
댓글은 로그인 사용자만 작성 가능합니다. 로그인하기