pig's diary

何でも忘れるので万年初心者ね

PromiseとDeferredを表面的に実装してみた。

promiseにはpromise/A という小さなルールがあって、それに則って皆ライブラリを作っているみたいだけど、それはいいからとにかくthen したくなった。

主に、promiseオブジェクトのありかたが本家を無視しています。promiseオブジェクトは、thenを一度しか呼べないルールなんじゃないか。で、thenしたら 新たなpromiseオブジェクトをnew してreturn するんじゃないかと思ってる(全部詳細未確認)。

TODO: あとでちゃんと kriskowal/q のソースよむ。

var util = require('util');


/**
 * @constructor
 */
var Promise = function(startValue) {
  this.startValue = startValue;
  this.chain = [];
};

// thenすると、ステップを蓄積していく。
Promise.prototype.then = function(f) {
  this.chain.push(f);
  return this;
};

// doneすると、最後のステップを挿入したあと、実行を開始する。
Promise.prototype.done = function(f) {
  this.then(f).call_(this.startValue);
};

/**
 * @protected
 */
Promise.prototype.call_ = function(val) {
  var me = this;

  // nextTickから実行開始。
  process.nextTick(function() {
    var rv = me.chain.shift()(val);
    if (me.chain.length != 0) {
      // 蓄積されたステップを、nextTick ごとにcallしていく。
      me.call_(rv);
    }
  });
};

(new Promise).then(function() {
  return 'a';
}).then(function(a) {
  return a + 'b';
}).then(function(b) {
  return b + 'c';
}).done(function(out) {
  console.log(out); // 'abc'
});



/**
 * @constructor
 * @extends {Promise}
 */
var Deferred = function() {
  Promise.call(this);
};
util.inherits(Deferred, Promise);

/**
 * @protected
 */
Deferred.prototype.call_ = function() {
  var me = this;
  var args = Array.prototype.slice.call(arguments);

  // 次の関数を呼ぶための準備をする。doneなら無視。
  if (me.chain.length >= 2) {
    args.unshift(function next() {
      if (me.chain.length > 0) {

        // next が呼ばれたら、次のステップに移れるように。
        me.call_.apply(me, arguments);
      }
    });
  }

  // nextTickから実行開始。
  process.nextTick(function() {
    // 蓄積した最初の関数を実行。
    me.chain.shift().apply(null, args);
  });
};


(new Deferred).then(function(next) {
  setTimeout(function() {
    next('a');
  }, 800);
}).then(function(next, a) {
  util.print(a);
  setTimeout(function() {
    next('--', 'b');
  }, 800);
}).then(function(next, and, b) {
  util.print(and + b);
  setTimeout(function() {
    next('c');
  }, 800);
}).done(function(c) {
  console.log(c);
});
// 'a', '--b', 'c' と非同期的に出力される