관리 메뉴

HAMA 블로그

Future 패턴과 AngularJS 본문

Angular & React

Future 패턴과 AngularJS

[하마] 이승현 (wowlsh93@gmail.com) 2015. 5. 31. 11:07


Future 패턴같은것은 이미 컨셉이 나온지 수십년이나 흘렀는데 , 요즘 와서 빛을 발하고 있다.
멀티코어가 일반화되면서 멀티쓰레드개발이 점점 강조되고 있으며, 또한 통신(웹개발포함)향 프로그래밍에서는 비동기적인 로직이 더욱 효율적이기 때문이다. 특히나 현재 대세인 자바스크립트는 비동기프로그래밍에 감을 못잡고는 적극적으로 활용하기 불가능하다.  ( 자바스크립트처럼 어렵고 비직관적인 언어가  짱의 지위에 올랐다는게 개인적으로 굉장히 흥미롭게 생각한다.) 먼저 자바로 Future 패턴에 대해서 살펴보고 이어서 AngularJS 의 $q / Promise 에 대해 알아보자.

이번글은 코드로 말해요가 될것이다. 개발자라면 글 보다는 코드만 보여주면 금방 감을 잡지 않은가~:-)


1. 자바를 이용한 Future 패턴 


먼저 Future 패턴의 클래스 다이어그램을 보자.  

( IFood / Future / ReadFood 의 관계는 Proxy 패턴이다. 매우 많은 패턴이 저런 모양을 가지고있다. 컴포지트,데코레이터,프록시, 어댑터, 인터프리터 등등  결국 모양 과 패턴은 전혀 상관이 없다. "의도" 가 중요한것이다.)


당신은 주말에 꿀 같은 휴식을 취하고있다.  복면가왕을 보다보니 출출해서 피짜헛에서 피자를 하나 시켜먹어야겠다고 생각했다.  

1. 주문을 한다.

2. 문앞에서 하염없이 기다린다.


맞나?  

그렇지 않다. 우리는 보통 주문을 한 다음에 보던 TV 를 계속보던가 ,  똥싸러 가거나  다른 일을 하게된다. 

이런걸 비동기적인 행동이라고 한다. 위에 하염없이 기다리는것을 동기적( 블로킹 되었다라고도 ) 이라고 한다.

다시 저 위의 다이어그램을 살펴보자. 


클라이언트 ( 당신 ) 은  레스토랑( 피짜헛) 에 주문을 하고  기다리는 코드는 대략 아래와 같다. 

Restaurant rest = new Restaurant();

Pizza pz = rest->requestPizza();     // 요기서 피자 올때까지 계속 기다림!!

eat(pz); // 피자 오면 냠냠!!!! 

이런식으로 코드가 진행된다면  직관적이라  코드리딩이 쉽고 에러날 확률이 줄어들겠지만  피자만들고 배달오는 시간동안 기다려야하기때문에 굉장히 지루하면서, 시간낭비라 할수있다. 그래서 결국 피자만드는 시간이 빠르면 빠를수록 동기적으로코딩하는게 나을테고, 느리면 느릴수록 비동기적으로 코딩하는게 나을것이다. 


그럼 비동기적으로는 어떻게 될까?  위의 UML 에 나온 클래스들을 기반으로 대략 코드를 보자.

Restaurant rest = new Restaurant();

Future  ticket = rest->requestPizza(); //전화로 주문을 한후에, 티켓을 받는다.(티켓에 어떤 능력이있다고 하자)   

boolean b = future->isComplete();     // 티켓을 통해서 완료됬는지 (배달이 집앞에 왔는지) 알수있다. 

if(b){

eat(future->getPizza());    // 집앞에 배달이 왔으면 냠냠 먹는다. 

}

else{                                      // 배달 안왔으면 하던일을 한다.

      .... TV 를 보거나 똥을 싼다 ....

       .... // 좀 지나서 

while(!future->isComplete()){  // 시간이 지나서 배달 올 때쯤에 다시 확인해본다.   

          Sleep(100);                       // 아직 안왔으면 올때까지 기다린다.   

}

eat(future->getPizza());         // 피자를 냠냠!!! 

}

 이런식으로 Future 객체를 사용하는것이 Future 패턴이다.  보면 알겠지만  무엇인가 일을 시킨후에 바로 리턴받고 자기 할일을 한다. 리턴받은 객체(Future) 를 통해서 간간히 자기가 시킨 일의 결과를 확인할수있게된다. 

조금 코드가 복잡해졌지만, 시간 활용도가 매우 높아졌다. 


기본적인 Future 패턴을 살펴보았는데,  여기에 좀 더 양념을 뿌려볼까 한다. 위에 코드를 보면 isComplete 를 통해서 계속 완료됬는지 안됬는지 신경써야한다는 단점이 있다. 어떻게 해결할까? 

"콜백" 함수를 이용하면 좋을거 같다.  다음 코드를 살펴보자 (pseudo코드) 

Restaurant rest = new Restaurant();

rest->requestPizza( new SuccessHandler() {

                                                                 @Override

                                                                 public void eat(Pizza piz) { 

//피자 냠냠!!

                                                                 }

 });

이런식으로 애초에 콜백함수를 요청함수에 넣어서 놓고 있어버릴수도있다.  근데 이것의 단점은 무엇일까??

내가보기엔 이렇게 하면 어떤 요청을 하고나서 내가 더 세세하게 제어할수있는 기회가 줄어들거 같다. 

자 이쯤되면  위의 Future 클래스들은 어떻게 생겼는지 궁금할것이다. (궁금하지 않으면 곤란하다) 

내부소스를 살펴보도록하자.

public class Restaurant {

public IFood requestPizza(){

final Future future = new Future();

new Thread(){

public void run(){

RealFood real = new ReadFood();

real.makePizza();

real.deliver();

future.setReadData(real);

}.start();

return future;

}

 이해가는가 ??  

1.쓰레드 A 는 Future 객체를 만들어서 바로 리턴해준다.

2.쓰레드 B 가 새로 생겨서 진짜 피자를 만들고, 배달하는 작업을 한다. 

3.쓰레드 B 는 피자를 만들면 Future 객체에 집어넣어준다. 

4.쓰레드 A 를 통해서 Future 객체를 가져간 고객은 기다리다가, 완료를 확인하고 먹는다.


이제 Future 클래스를 살펴보자.

public class Future{

private RealFood real = null;

private boolean ready = false; 

public synchronized void setRealData(ReadFood real){    

if(ready){

return ;

}

this.read = real;

this.ready = true;

notifyAll();

}

public synchronized Pizza getPizza(){

wait();

return real.getPizza();

}

public synchronized boolean isComplete(){

return ready;

}

}

당신이 Future 객체를 받은후에 isComplete 를 통해서 피자가 도착했는지 확인하는것이 저 코드인데 

보다시피 , 실제 피자가 들어갈때가지는 false 를 리턴한다.  실제 피자가 들어가면 notify 를 해주고 true 로 세팅해준다. 여기까지가 Future 패턴의 기본이다.


다시 Restaurant  클래스를 보도록하자.  지금 예제는 단순예제이기때문에 Restaurant 클래스의 requestPizza 안에 저렇게 워커쓰레드가 돌아가지만 좀더 확장하려면  자신만의 큐를 가지고있는 능동형 객체에 할일을 넘겨주는 방법이 깔끔하다. 그러기 위해서 ActiveObject 를 이용하면 될것이다. (http://okky.kr/article/279263 참고) 


2. AngularJS 와 비동기 기법들 

AngularJS 는 프런트엔드 MVC프레임워크로 , 음 저런 딱딱한 용어보다는 Ajax 코딩할때 굉장히 쉽고 편하게 코딩을 도와주는 놈이다. 프런트엔드 코딩을 엄청 단순화 해준다. 더 와닿는가??

예를들어서 Rest 서버로 get 호출을 하는 ajax 코딩을할때, 넘겨받은 데이터를 Display 해줘야하는 과정을 거치는데 양방향 바인딩을 통해서 엄청~~나게 이 작업을 쉽고 직관적으로 해준다.

이제부터 AngularJS 의 promise 를 살펴보도록하자.  위에서 살펴본 Future가 promise 라고 생각하면 된다.(확장됨) AngularJS  의  $http 를 통해서 Ajax 코딩을 할때 비동기적으로 하게되는데, 그 전에 $q 와 promise 를 살펴보자.


$q 서비스 

AngularJS는 Common JS Promises/A 스펙에 대한 구현 API를  $q 서비스를 이용해 제공한다. $q 서비스는 Kris Kowal의 Q.js에 영감을 받아 구현되어 이름이 $q 이다. $q 서비스는 Q.js에 비해 제공하는 기능은 적지만 AngularJS의 scope 모델의 데이터 바인딩에 대한 처리가 최적화돼 있다.


다음 소스를 보자!

var Person = function ( name ) {
this.eat = function (food) {                                      //  주문이 도착했을때 발생할  콜백함수.
 console.info(name + " is eating " + food);
}
};

....

var pizzaOrderSystem = $q.defer();
var pizzaDelivered = pizzaOrderSystem.promise;

pizzaDelivered.then(john.eat);     // 피자가 도착했을때 발생할  콜백함수 등록 

pizzaOrderSystem.resolve("슈퍼슈프림");   // 피자가 도착했다고 알림 


 $q.defer();   =>  &q 서비스는  defer 객체를 가지고있는데, 이 놈이 무엇을 하는지 집중해 보자. 
 
defer 는  크게 2가지를 가지고있는데 , 

첫째.  defer 는 promise 객체를 가지고있다.  위에 언급했듯이 promise 는 future + 알파이다.  

피자가게를 다시 생각해보자.  피자를 시키고 future 를 받은것처럼 여기서는 promise 를 받게된다.

promise 에 콜백을 등록하여 미래에 일어날 일에 대한 구현을 등록해놓는다

promise.then(success, fail);    // 피자주문이 성공했을때 와 실패했을때 각각 콜백함수를 등록한다. 

두번째, defer 는 resolve 와 reject 함수를 가지고있다. 이것은  위의 자바 Future 패턴에서 없던 내용인데 ,  resolve 를 통해서 일 (피자도착) 이 완료됬다는걸 알려준다. 위에서 pizzaOrderSystem.resolve("슈퍼슈프림");  를 보면 슈퍼슈프림 피자가 도착했다고 알려주는것을 볼수있다. 


좀 더 구체적으로 피자 가게에 관한 자바스크립트 코드를 살펴보도록하자.

var Restaurant = function ( $q , $rootScope){

var currentOrder;

this.takeOrder = function (orderedItems) {

currentOrder = {

deferred : $q.defer(),

items.orderedItems 

};

return currentOrder.derred.promise;

};


this.deliverOrder = function(){

currentOrder.deferred.resolve(currentOrder.items);

$rootScope.$digest();

};

};

....

pizzaStore = new Restaurant($q, $rootScope);                        // 피자가게 객체를 생성. 

var pizzaDelivered = pizzaStore.takeOrder('슈퍼스프림');      // 피자를 주문합니다. promise (future) 객체를 받음. 

pizzaDelivered.then(john.eat);                                                 // 피자가 도착했을때 일어날 콜백함수를 등록

pizzaStore.deliverOrder();                                                   // 피자가 도착했다고 강제로 알려줌. 

코드에서 deliverOrder  함수를 보면 내부에서 resolve 를 호출하는것을 볼수있다. 어떤 일(피자도착)이 완료됬다고 알려준다. 완료됬다는 신호와 동시에 promise (pizzaDelivered)  에 등록한 함수가 실행되는데 그게  pizzaDelivered.then(john.eat);  이다.

대략 AngularJS 에 지원하는 Promise 를 살펴보았다. 이제 이걸 가장 많이 사용하는것에 대한 예제를 살펴보도록하자. 어디에 많이 쓰일까? 그렇다.  Ajax 호출을 한후에 기다렸다가 , 성공했다고 알려오면 성공콜백함수를 호출하여 데이터를 바꾸어주고 바뀐 데이터를 토대로 디스플레이가 바뀌는곳에 사용된다. 

아래 서비스 코드를 보자. 

angular.module('demo-app', [])
  .factory('userService', function($http, $log, $q) {
    return {
     getUser: function(userId) {
    
       var deferred = $q.defer();
       $http.get('/api/users/' + userID)
         .success(function(data) { 
            //요청이 성공하면 약속을 지키고 별도 데이터를 전달한다.
            deferred.resolve({
               name: data.name,
               address: data.address});
         }).error(function(msg, code) {
            //요청이 실패하면 약속을 취소하고 메시지를 전달한다.
            deferred.reject(msg);
            $log.error(msg, code);
         });
       //해당 deferred 객체의 약속을 반환한다.
       return deferred.promise;
     }
    }
   });

위에  $http.get('/api/users/' + userID) 와 같이 Ajax 호출을 한후에 성공하면 성공 콜백함수가 호출된다. 아래처럼

 .success(function(data) { 
            deferred.resolve({
               name: data.name,
               address: data.address});
  })

deferred.resolve 를 실행하는것을 볼수있다. resolve 는 무엇을 하는거라고?? 그렇다  성공했다고 promise 객체에 알려주는역할을 한다. 저 위에 코드에는 나와있지 않지만 UserService 를 사용하는곳에서 getUser 를 호출한후에 promise 객체를 받았을텐데 그곳에서 아마 promise 객체에 성공시 콜백함수를 등록했을것이다. 그 콜백함수를 resolve 를 통해서 실행시켜 줄 것이다. 


참고로 저렇게 서비스(팩토리) 를 이용해서 코딩하지 않고 컨트롤러에서 직접 통신로직을 구현한다면 defer 는 필요없다.

controller('userListCtrl',function($scope, $http) {
    var reqPromise = $http.get('sample.json');        // $http.get 을 통해서 promise (future) 를 받음.

    reqPromise.success(function(data) {               // promise 에 성공했을때 실행할 콜백등록 
      $scope.userList = data;
    });

    reqPromise.error(function(data) {
      console.error("Ajax 에러 발생");
    });
});

보다시피 위에는 defer 라든지 resolve 가 사용되지 않았다. 이상 자바언어를 통해서 Future 패턴을 살펴봐서 인사이드를 이해하고  AngularJS 의 promise 와 $http 를 통해 future 패턴을 활용해보았다. 




레퍼런스 

http://en.wikipedia.org/wiki/Futures_and_promises

http://webframeworks.kr/tutorials/angularjs/angularjs_promise_deferred/ 

http://www.yes24.com/24/goods/13527426?scode=032&OzSrank=4

http://www.yes24.com/24/goods/2922297?scode=032&OzSrank=7





'Angular & React ' 카테고리의 다른 글

Kendo UI 를 Angular2 와 함께 사용하기  (0) 2017.03.17
AngularJS 2 무엇이 바뀔것인가?  (0) 2015.05.22
Comments