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;

}

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


댓글 없음:

댓글 쓰기