Hatena::Groupjavascript

JavaScriptで遊ぶよ

 | 

2009-04-11

JSDeferredのソース読んでる・その2

19:37

前回。

とりあえず JSDeferred の頭の部分を貼っとく。

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

	next  : function (fun) { return this._post("ok", fun) },
	error : function (fun) { return this._post("ng", fun) },
	call  : function (val) { return this._fire("ok", val) },
	fail  : function (err) { return this._fire("ng", err) },

	cancel : function () {
		(this.canceller || function () {})();
		return this.init();
	},

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

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

色々読んでわかったのだが、(当然の話だけど) この部分が JSDeferred のキモだ。しかしここだけ何度読んでも、JSDeferred の内容はまったく理解できない。

とりあえず昨日の続きの部分

next(function func1(){
  /* 処理 */
})
.next(function func2(){
  /* 処理 */
})

next(function func1(){
  /* 処理 */
})
.wait(0)   // ←これ!
.next(function func2(){
  /* 処理 */
})

この違いから見ていきたいのだけど、その前に疑問がある。


この wait 関数がどこで定義されているか

この wait を呼んでいるのは next の返り値 (Deferred インスタンス) ということになる。でも、Deferred.prototype には wait は明示的には定義されていない。

これはソースのずっと下のほうにあった。

Deferred.register = function (name, fun) {
	this.prototype[name] = function () {
		return this.next(Deferred.wrap(fun).apply(null, arguments));
	};
};

Deferred.wrap = function (dfun) {
	return function () {  // ←この関数を便宜的に A と呼ぶことにするよ
		var a = arguments;
		return function () {
			return dfun.apply(null, a);
		};
	};
};

Deferred.register("loop", Deferred.loop);
Deferred.register("wait", Deferred.wait);

最後の一行で Deferred.prototype.wait が定義されていると予想がつく。これを読み解く。

Deferrd.register 関数の中でやっていることは、this (つまり Deferred) の prototype に "wait" を定義して (やっぱり Deferred.prototype.wait はここで定義されていた)、next (Deferred.next ではなくて Deferred.prototype.next のほう) を返す関数をセットすること。


この next に渡している関数のことを見ていく

Deferred.wrap を見ると、その中では return function〜 となっている。その関数を便宜的に A と呼ぶことにする (A = Deferred.wrap(Deferred.wait) と思えばいい)。dfun には Deferred.wait が渡されている。

Deferred.prototype.wait が実行されたとき、Deferred.wrap(fun).apply(null, arguments) が呼ばれる。この arguments は、とりあえず wait の場合だったら 0 とかになる。

つまりここまでは A.apply(null, 0) と同じ、もっと言うと A(0) と同じ。

A はまた別の関数を返す。その関数とは、

return function () {
	return dfun.apply(null, a);
	// つまり return Deferred.wait.apply(null, 0);
	// つまり return Deferred.wait(0);
};

である。


というわけで
next(function func1(){
  /* 処理 */
})
.wait(0)

これは、

next(function func1(){
  /* 処理 */
})
.next(function(){
  return Deferred.wait(0)
})

ということだった。

長ったらしかったけど、Deferred.register というのはつまり「任意の関数を Deferred チェーンの中で使えるようにするもの」だった。


チェーンを分割する

まず、Deferred.wait を見る。

Deferred.wait = function (n) {
	var d = new Deferred(), t = new Date();
	var id = setTimeout(function () {
		clearTimeout(id);
		d.call((new Date).getTime() - t.getTime());
	}, n * 1000);
	d.canceller = function () { try { clearTimeout(id) } catch (e) {} };
	return d;
};

新しい Deferred インスタンスを作って、setTimeout で n 秒後にそれを call する。

call の引数は正確な遅延時間をミリ秒で渡している。


チェーンの中で使われた場合
next(function func1(){
  /* 処理 1 */
})
.wait(0)  // .next(function implicit(){ return Deferred.wait(0) }) と同じこと
.next(function func2(){
  /* 処理 2 */
})

となっている場合を考える。昨日の例に倣ってチェーン図を書くと、

+ d1
| - callback.ok = func1
| + _next = d_implicit
  | - callback.ok = implicit
  | + _next = d2
    | - callback.ok = func2

となる。

(d_implicit というのは「暗黙に」呼ばれた next によって作られた Deferred インスタンスであることを示している)


チェーンの実行。

ここで重要なのが、この時点ではまだ func1 や implicit 関数の評価は行われておらず、チェーンを構築しただけである。つまり、遅延実行は行われていない。

遅延実行が行われるのは、最初の next による setTimeout (高速のほうだと img.onerror とか) からである。

その setTimeout では d1.call() として func1 を実行し、次に

if (this._next) this._next._fire(next, value)

の部分で d_implicit を fire する。

このとき、昨日の例と違うのは、implicit 関数がまた別の Deferred インスタンス (便宜的に d_wait と呼ぶことにする) を返していること。( Deferred.wait の最後の部分の return d があるため)

Deferred.prototype._fire の上の部分が

value = this.callback[okng].call(this, value);  // これはつまり以下と同じ意味
// value = d_implicit.callback.ok.call(d_implicit,value)
// value = implicit.call(d_implicit,value)
// value = (function implicit(){ return Deferred.wait(0) }).call(d_implicit,value)
// value = d_wait

となるので、下の部分はこうなる。

if (value instanceof Deferred) {
	value._next = this._next;
	// つまり d_wait._next = d_implicit._next (= d2)
} else { // こっちは実行されない
	if (this._next) this._next._fire(next, value);
}
return this;

なんと、チェーンの動的な書き換えが行われていた。

具体的には、上のチェーンは2分割されてこんな感じになっていたのだった。

最初の next によるチェーン
+ d1
| - callback.ok = func1
| + _next = d_implicit
  | - callback.ok = implicit

wait によるチェーン
+ d_wait
| - callback.ok = Deferred.ok
| + _next = d2
  | - callback.ok = func2

この発想には驚いた。

this._next._fire(next, value) が実行されていないことによって、チェーンが一旦途切れるのだけど、そこはちゃんと wait の中で setTimeout して d.call しているので、別にチェーンが出来るということ。


次回は

今日やってしまいたかった error のことと、出来れば loop や parallel の話へ。


追記

総括も書いたよ。

 |