<%Dewplayer(javascript)%> 肉少なめ | Item - JSDeferredを読もうとして

JSDeferredを読もうとして

Title :
JSDeferredを読もうとして
Posted on :
2008-04-22
Author :
NKJG
Category :
Webメモ
Hatena Star :

本文

JSDeferred ( JSDeferred - CodeRepos::Share - Trac ) をちょっと前から触ったり眺めたりしてますが、やっぱりちゃんと読んでみようという話。これも三日坊主になりそう。コメントありのver.2.1を使用。全部メモ。

83行目

ここまで全部コメント。

function Deferred () { return (this instanceof Deferred) ? this.init(this) : new Deferred() }

関数Deferredの定義。クラスDeferredのコンストラクタ。初期化処理はDeferred.prototype.initにある。

コメントにはDeferred();new Deferred();のショートハンドである、的なことが書いてある。Deferred();を実行すると、(this instanceof Deferred)の値はfalseになり、三項演算子の後のほうが実行され、結果的にnew Deferred();の結果が返ってくる。

JavaScript の new 演算子の意味: Days on the Moonを参考にして、無名関数・引数ありの場合で書き直すとこんな感じ?

function() {
  if (this instanceof arguments.callee) {
    return this.init.apply(this, arguments);
  } else {
    var Constructor = function() {};
    Constructor.prototype = arguments.callee.prototype;
    var instance = new Constructor();
    var result = arguments.callee.apply(instance, arguments);
    return (result instanceof Object) ? result : instance;
  }
}

なんかすっごい長くなった。

84行目〜125行目

Deferred.prototypeの定義。

85行目〜92行目

初期化処理を行うDeferred.prototype.initの定義。

	init : function () {
		this._next    = null;
		this.callback = {
			ok: function (x) { return x },
			ng: function (x) { throw  x }
		};
		return this;
	},

94, 95行目, 104行目〜108行目

	next  : function (fun) { return this._post("ok", fun) },
	error : function (fun) { return this._post("ng", fun) },

nextメソッドとerrorメソッドの定義。細かいことは_postメソッドにある。

ちょっと飛ばして、その_postメソッドについて。

	_post : function (okng, fun) {
		this._next =  new Deferred();
		this._next.callback[okng] = fun;
		return this._next;
	},

これは見たまま。this._nextの値はDeferredクラスの新しいインスタンス、で、それが返ってくる。

新しいインスタンスが返ってくるので、以下のようにつなげられる。

var testDeferred = new Deferred();
testDeferred.
next(function() { /* ... */ }).
next(function() { /* ... */ });

95, 96行目, 110行目〜124行目

	call  : function (val) { return this._fire("ok", val) },
	fail  : function (err) { return this._fire("ng", err) },
	_fire : function (okng, value) {
		var self = this, next = "ok";
		try {
			value = self.callback[okng].call(self, value);
		} catch (e) {
			next  = "ng";
			value = e;
		}
		if (value instanceof Deferred) {
			value._next = self._next;
		} else {
			if (self._next) self._next._fire(next, value);
		}
		return this;
	}

作ったチェーンを開始するメソッドcall、あるいはエラーを処理するメソッドfail、そしてその中身。普通に使う分にはあまり意識しなくていいっぽい。

_fireメソッドは内側でself.callback[okng]を実行している。okngは文字列'ok'または'ng'。実行したとき何かthrowされているとself._next.callback.ngが、そうでないならself._next.callback.okself._next._fire経由で実行される。

初期化直後だとself.callback['ok']は第1引数をそのまま返す関数で、self.callback['ng']は第1引数をそのままthrowする関数になっている。なので、以下の2つは結果的に同じ動きになりそう。

var testDeferred = new Deferred();
testDeferred.next(function(){ alert('asdf'); });
testDeferred.call();
var testDeferred = new Deferred();
testDeferred._next = new Deferred();
testDeferred._next.next(function(){ alert('asdf'); });
testDeferred.call();

self.callback[okng]の実行でDeferredクラスのインスタンスが返されたりthrowされたりすると、self._next._fireは実行されず、そのインスタンスが返される。ただし、そのインスタンスは_nextとしてself._nextの値を持つ。_fireメソッドは、この返されたインスタンスの_fireメソッドを実行する機能を持たない。

var testDeferred = new Deferred();
testDeferred.next(function() {
  alert('asdf');
  var subDeferred = new Deferred();
  return subDeferred.next(function() { alert('fdsa'); });
}).next(function() {
  alert('asdffdsa');
});
testDeferred.call();

上のようにするとalert('asdf');は実行されるがalert('fdsa');alert('asdffdsa');は実行されない。