레이블이 promise pattern인 게시물을 표시합니다. 모든 게시물 표시
레이블이 promise pattern인 게시물을 표시합니다. 모든 게시물 표시

2014년 12월 26일 금요일

Q.Promise(function(resolve,reject,notify) {...})에 대한 메모

개요
Q.Promise의 사용에 있어서 역할이 혼란스러워 정리를 해두고자 한다.

수순

 var promise = Q.Promise(function(resolve,reject,notify) {  
   ???  
 });  

상기 ???에 대해, resolve, reject, notify를 대입해 promise체인에 대한 동작을 확인한다.

1. return reject();

 promise.catch(  
     function(ex) {  
       console.log("catch");    
     })  
     .fail(function(ex) {  
       console.log("fail");    
     })  
     .then(function(data) {  
       console.log(data);  
     });  
결과 :
catch
undefined

 promise  
   .fail(function(ex) {  
     console.log("fail");      
   })  
   .then(function(data) {  
     console.log(data);    
   });  
결과 :
fail
undefined.

 promise  
   .then(function(data) {  
     console.log(data);
   })  
   .fail(function(ex) {  
     console.log("fail");
   })  
   .catch(function(ex) {  
     console.log("catch");
   });

결과 :
fail

 promise  
   .then(function(data) {  
     console.log(data);  
   })  
   .catch(function(ex) {  
     console.log("catch");  
   });  
결과:
catch

-> 에러를 분리해서 체이닝 할경우, 실패가 뒤로 가야하는것을 알 수 있다. 에러보다 뒤에있는 then은, 실행이 완료된 후 다음 동작을 의미한다. catch, fail을 then과 병용할때는 catch,fail이 then의 뒤에 붙을 필요가 있다. 그리고 이런 수순이 혼란스러울 정도라면 체이닝을 할게 아니라 then의 2,3번째 callback에서 호출해야 맞지 않을까?

 promise  
    .then(function(data) {  
      console.log(data);  
    },function(err) {  
      console.log("err");  
    })  
    .catch(function(ex) { // or fail  
      console.log("catch");  
    });  
결과
err
-> catch/fail가 불려지지 않는다.

2. throw new Error("msg");
var promise = Q.Promise(function(resolve,reject,notify) {  
   throw new Error("test throw");  
});  

 promise  
    .then(function(data) {  
      console.log(data);
      res.json(data);  
    })  
    .fail(function(ex) {  
      console.log("fail");  
    })  
    .catch(function(ex) {  
      console.log("catch");  
    });  
결과
fail

 promise  
    .then(function(data) {  
      console.log(data);
      res.json(data);  
    })  
    .catch(function(ex) {  
      console.log("catch");  
    });  
결과
catch
-> throw역시 fail로 catch된다.

3. 그 외, notify
 var promise = Q.Promise(function(resolve,reject,notify) {  
   console.log("call");  
   notify(1); // some progressive  
   notify(2); // some progressive  
   return resolve("Apple");
});  

promise  
    .then(function(data) {  
      console.log(data);  
    },function(err) {  
      console.log("err");  
    },function(notification) {  
      console.log(notification);  
    })  
    .fail(function(ex) {  
      console.log("catch");  
    });  
결과
Apple
-> 1,2 notify가 호출되지 않았다...?

var promise = Q.Promise(function(resolve,reject,notify) {  
   
    Q.delay(1000)
     .then(function() {  
       notify(1);  
     })
     .delay(1000)
     .then(function() {  
       notify(2);
     })
     .then(function() {  
       resolve("apple");  
     });  
 });  

promise
    .then(function(data) {  
      console.log(data);
      res.json(data);
    },function(err) {  
      console.log("err");
      res.json(err);
    },function(notification) {  
      console.log(notification);
    });  
결과
1
2
apple
-> 각 결과가 1초 간격을 두고 불러졌다. 비동기에서만 notify가 반응하는듯 하다.

var promise = Q.Promise(function(resolve,reject,notify) {  
    Q.fcall(function() {  
      throw new Error("throw in another call stack");  });  
      return resolve("data");  
    });  

promise  
    .then(function(data) {  
      console.log(data);
      res.json(data);
    })  
    .catch(function(ex) {  
      console.log("catch");
    });  
결과
data

당연히 다른 callstack에서 throw 한 내용에 대해서는 catch 하지 않는다.

var promise = Q.Promise(function(resolve,reject,notify) {  
     Q.fcall(function() {  
       return reject(new Error("throw in another call stack"));    
       resolve("data");  
     });  
   });  

promise
    .then(function(data) {  
       console.log(data);
       res.json(data);
    })  
    .catch(function(ex) {  
      console.log("catch");
    });  

다른 call stack에서의 에러 호출은 상기와 같은 느낌으로 실시해야하고, 이후의 코드가 실행되지 않게 하기위해 (throw가 그렇듯) return하여 흐름을 끊어줄 필요가 있다.

var promise = Q.Promise(function(resolve,reject,notify) {  
    Q.fcall(function() {  
      return reject(new Error("throw in another call stack"));
      resolve("data");  
    })
    .then(function() {  
      console.log("is this code called?");  
    });  
 });  
 promise
    .then(function(data) {  
      console.log(data);    res.json(data);  
     })  
    .catch(function(ex) {  
      console.log("catch");  
     });  
결과
is this code called?
catch

-> 아... 위에서 reject를 리턴했다고 다음 then이 실행되지 않는건 아닌가보다.

var promise = Q.Promise(function(resolve,reject,notify) {  
    Q.fcall(function() {  
      return reject(new Error("throw in another call stack"));
      resolve("data");
    }).then(function(info) {  
      console.log("is this code called?");
      console.log(info);
      return "dummy";  
    }).then(function(info) {  
      console.log(info);  
    });  
});  

promise
    .then(function(data) {  
      console.log(data);
      res.json(data);  
    })  
    .catch(function(ex) {  
      console.log("catch");
    });  
결과
is this code called?
undefined
dummy
catch

-> reject의 return값은 void인듯하다. 처음 info는 아무 정보도 가지고 있지 않다. 다음 info는 위에서 return한 값 "dummy"를 가지고 있다. 이들은 일종의 event chain으로 sync동작하게된다. 따라서 catch가 가장 마지막에 나오고 있다. 중간에 delay를 끼워 yield에 상당하는 동작을 하게되면 상황은 바뀌게 될것이다.

var promise = Q.Promise(function(resolve,reject,notify) {  
     Q.fcall(function() {  
       return reject(new Error("throw in another call stack"));
       resolve("data");
     }).then(function(info) {  
       console.log("is this code called?");
       console.log(info);
       return "dummy";
     }).delay(1)
     .then(function(info) {  
       console.log(info);
     });  
 });  

promise
    .then(function(data) {  
      console.log(data);
      res.json(data);  
    })  
   .catch(function(ex) {  
       console.log("catch");
    });  
결과
is this code called?
undefined
catch
dummy

-> 예를 들자면 이런느낌이다.

2014년 11월 6일 목요일

Promise Pattern

node.js와 같은 이벤트 기반의 언어를 다루게되면,
순차적인 코드 실행순이 아닌 이벤트 기반의 처리에 맏닥뜨리게 됩니다.

하나의 스레드를 중심으로 여러 처리를 이벤트가 발생할때 마다 처리를 수행하는 node.js에 있어서는 필수 불가결한 구조이지만, 이러한 구조는 일반적인 동기식 처리코드에 비해서 어떤 내용이 우선적으로 처리될지, 순차적인 코드 정리가 어지럽혀질 가능성이 큽니다.

무슨말인지 이해하기 힘들다는 생각이 들면, 예를 들어 아래 사이트에 거재된 callback-hell인 코드를 봐주시는게 이해가 빠를거라 생각이 듭니다.

http://callbackhell.com/

코드가 지저분해질 뿐만 아니라, 비동기이기 때문에 어느 코드가 우선적으로 수행될지 직관적으로 알수가 없습니다.

이러한 비동기적 환경 하에서,

여느 디자인 패턴이 그렇듯이 순전히 코드를 깔끔하게, 사람이 알아보기 좋게 하기 위해서 비동기적인 환경에서 순차적인 코드수행의 보장과 정리를 하기 위해 나온 패턴이 프로미스 패턴입니다.

사실 이러한 패턴자체는 제창된지는 제법 되었고, jQuery등에서는 이미 예전부터 사용되고 있습니다.

$("#id").delay(1).fadeOut("slow");

이러한 코드가, 프로미스 패턴의 한 사례입니다.

프로미스 패턴은 현재 어느정도 표준화가 진행되어, Mozilla산하의 표준에서나, nodejs에서 또한 0.12버전부터는 new Promise(...)로 이용이 가능해진다고 합니다.(현 시점에서는 스테이블이 아닙니다)

http://www.html5rocks.com/ko/tutorials/es6/promises/
http://d.hatena.ne.jp/jovi0608/20140319/1395199285

개인적으로는 아직 nodejs의 표준으로 이용할수가 없기 때문에, 대표적인 라이브러리인 q를 사용하고 있습니다.

아래에 프로미스 패턴을 사용해 sails.js의 모델에서 데이터의 검색과 분기로 인한 크리에이션을 정리한 코드를 첨부합니다.

Fish는 sails.js의 하나의 "모델"입니다.
hi function은 sails controller로 인한 RESTful API의 접근으로 접근이 가능한 메소드입니다.
수순적으로 로컬에 정의된 Find()로부터 파생된 검색 내용을 가지고, 없으면 조건부로 생성을 실시하는 수순의 처리 루틴을 타고 있습니다.

데이터베이스로의 접근은 MVC패턴상의 sails.js의 모델을 경유하여 접근하고있고, 데이터 취득자체는 비동기로 처리됩니다. 이 과정중에 프로미스로 인한 정리 수순이 들어가게 됩니다.

/**
 * TestController
 *
 * @description :: Server-side logic for managing tests
 * @help        :: See http://links.sailsjs.org/docs/controllers
 */
var Q = require("q");
var iz = require("iz");

module.exports = {

hi: function (req, res) 
{

    var fishname = "zako";
    var fishtype = "microfish";
    
    // ----------------------------------------------------
    // fishing example
    // ----------------------------------------------------

    Find(fishname)
    .then(

      // ---------------------------------------------------
      // loaded. it exists.
      // ---------------------------------------------------
      function(info) {
        console.log("exists. just loads");
        return Q.resolve(info);
      },

      // ---------------------------------------------------
      // failed. regenerate
      // ---------------------------------------------------
      function(err) {
        console.log("not exists, make new entity");
        return Create(fishname,fishtype);
      }

    ).then(

      // ---------------------------------------------------
      // find or created. show infos.
      // ---------------------------------------------------
      function(info) 
      {
        res.json(info);
      },

      // ---------------------------------------------------
      // creation failed.
      // ---------------------------------------------------
      function(err) 
      {
        res.json({code:"333",message:err});
      }

    );

    // ----------------------------------------------------
    // done
    // ----------------------------------------------------

},

};

// ------------------------------------------------------------

var Find = function(name) {

  var deferred = Q.defer();

  Fish.find(
    
    { 
        "name" : name
    },

    function(err,info) 
    {
        if(iz.empty(info)) 
        {
            deferred.reject(err);
        }
        else
        {
            deferred.resolve(info);
        }
    }

  );

 return deferred.promise;

};

// ------------------------------------------------------------

var Create = function(name,type) {

  var deferred = Q.defer();

  Fish.create(
    
    {
      "name":name,
      "type":type,
    },

    function(err,info)
    {
        if(iz.empty(info)) 
        {
            deferred.reject(err);
        }
        else
        {
            deferred.resolve([info]);
        }
    }

  );

  return deferred.promise;

}

// ------------------------------------------------------------