Promies는 비동기적인 요청과 응답의 복잡성을 다루는데 가장 좋은 해결책이다. 지난 포스팅 바보들을 위한 Promise 강의 - 도대체 Promise는 어떻게 쓰는거야? 에서 Promise가 무엇이고 어떻게 사용하는지에 대해서 설명했다.
AngualrJS 역시 이러한 비동기 작업/요청에 대한 처리를 위해 $q, $http 등의 서비스를 통해서 Promise 를 지원하고 있다. Kris Kowal's Q 에서 영향을 받아 $q 라는 이름으로 서비스명을 지었는데, 실제 Kris Kowal's Q 와는 두 가지 정도 차이가 있다.
하나는 $q의 경우 $rootScope.Scope 와 통합되어 있기 때문에 resolution 혹은 rejection 여부가 보다 빠르게 전파되며 이로인해 불필요한 브라우저 리페인팅을 막을 수 있고,
또 하나는 원래 Q에 비해 비동기 적업에 공통적으로 필요한 가장 중요한 기능들만을 포함해서 사이즈를 줄였다.
그런데 이 $q는 최근 ES6에서 규정된 Native Promise 와도 아주 약간의 차이가 있는데, 이번 포스팅에서는 이 부분에 대해서 살짝 다뤄보려고 한다.
앞서 바보들을 위한 Promise 강의를 충분히 파악했다면 아래의 내용은 아주 쉽게 이해될 수 있을 것이다.
ES6 Promise 와 $q의 차이
ES6 Promise의 가장 기본 형태로 우리는 아래와 같은 소스를 분석한 바 있다.
var _promise = function ( param ) { return new Promise( function ( resolve, reject ) { /*비동기를 표현하기 위해 setTimeout 함수를 사용*/ window.setTimeout( function () { /*파라메터가 참이면,*/ if ( param ) { /*해결됨*/ resolve("해결 완료"); } /*파라메터가 거짓이면,*/ else { /*실패*/ reject( Error("실패!!") ); } }, Math.random() * 2000 + 1000); }); }; _promise( true ).then( /*성공시*/ function (text) { console.log(text); }, /*실패시*/ function (text) { console.log(text); } );
이를 AngularJS에서 지원하고 있는 $q 로 변경하면 아래와 같이 나타낼 수 있다.
var _promise = function ( param ) { return $q( function ( resolve, reject ) { /*비동기를 표현하기 위해 setTimeout 함수를 사용*/ window.setTimeout( function () { /*파라메터가 참이면,*/ if ( param ) { /*해결됨*/ resolve("해결 완료"); } /*파라메터가 거짓이면,*/ else { /*실패*/ reject( Error("실패!!") ); } }, Math.random() * 2000 + 1000); }); }; _promise( true ).then( /*성공시*/ function (text) { console.log(text); }, /*실패시*/ function (text) { console.log(text); } );
어떤 차이가 있는지 눈썰미가 있는 사람은 쉽게 파악했을 것이다. 여기에서 ES6의 Promise와 $q의 사용에 있어서 차이가 있다면, 단지 new Promise 로 선언했던 것을 $q로 변경한 정도이다.
resolved 된 Promise 객체 리턴
그런데 항상 이렇게 변경되는 것만은 아니다.
이러한 케이스 외에도 resolvedPromise().then(....) 과 같은 형태로 chaining을 해줄 목적에서 resolve 된 promise 객체를 리턴하는 경우에는 약간 사용 형태가 달라진다.
function resolvedPromise () { return Promise.resolve({ userID : user.email, flightID : "UA_343223", date : "01/14/2014 8:00 AM" }); }
이를 $q로 표현할 경우 아래와 같이 나타낼 수 있다. deferred 객체를 생성하고 resolve된 promise를 반환하는 것이다.
function resolvedPromise() { var deferred = $q.defer(); deferred.resolve({ userID : user.email, flightID : "UA_343223", date : "01/14/2014 8:00 AM" }); return deferred.promise; }
이해를 돕기 위해 defer() 메서드를 사용하지 않고 굳이 $q 자체만 사용할 경우 아래와 같이 변경할 수 있다.
function resolvedPromise() { return $q( function (resolve, reject) { resolve({ userID : user.email, flightID : "UA_343223", date : "01/14/2014 8:00 AM" }); }); }
위와 같이 $q 로 콜백을 선언하고 resolve 메서드를 실행하면 promise 객체가 반환되기 때문에 동일한 결과를 얻는다.
Promise.all 메서드
또 여러개의 비동기 응답이 모두 처리되었을 때 우리는 Promise.all 메서드를 사용했었는데, 이 역시도 동일하게 $q.all 메서드를 사용하면 동일하게 처리가 가능하다.
Promise.all([promise1, promise2]).then( function (values) { console.log("모두 완료됨", values); });
위의 로직을 AngularJS의 $q 를 사용해서 표현하면, 아래와 같이 나타낼 수 있다.
$q.all([promise1, promise2]).then( function (values) { console.log("모두 완료됨", values); });
결론
AngularJS에서 비동기 작업을 위해 도입한 $q는 ES6의 것과는 약간의 차이가 있지만, 위에서 언급한 차이점에 대해서 인지한다면 큰 문제없이 AngularJS에서도 Promise 를 활용할 수 있을 것이다.
Promise를 활용해서 코딩하는 것은 다양한 패턴과 방법이 있다. 많이 사용해보고 기존 코드를 반복적으로 개선하는 작업을 통해 보다 나은 비동기 코드를 작성하는 것이 중요하다.