Hatena::Groupjavascript

JavaScriptで遊ぶよ

 | 

2009-04-10

JSDeferredのソース読んでる

18:41

とりあえず分かったこと。


Deferredチェーンの開始

JSDeferred による非同期処理のスライドにあるように、JSDeferred は基本的に next や wait のチェーンとして使う。

スライドによると、

Deferred.define();

next(function(){
  /* 処理 */
})

とやってグローバル変数を作れとあるけど、もちろんグローバルを汚染しなくてもいい。

var o = {}; // オブジェクト
Deferred.define(o);

for(var x in o)print(x);  // print 関数が定義されているとして
// parallel
// wait
// next
// call
// loop

o.next(function(){
  /* 処理 */
})

のように、オレオレオブジェクトに入れることもできる。もっと簡単に

Deferred.next(function(){
  /* 処理 */
})

とも書ける。(どれも実は Deferred.next をエクスポートしてるだけ)

この next はこうなってる。(実際にはこれじゃなくて高速のほうが使われるけど、アイデアは同じ)

Deferred.next_default = function (fun) {
	var d = new Deferred();
	var id = setTimeout(function () { clearTimeout(id); d.call() }, 0);
	d.canceller = function () { try { clearTimeout(id) } catch (e) {} };
	if (fun) d.callback.ok = fun;
	return d;
};

Deferred クラスの新しいインスタンスを作り、その callback.ok に引数として渡した関数をセットし、0ms 後に実行 ( d.call() の意味は後述)。

もし何もチェーンを続けなければ、そのまま setTimeout 部分が実行される。

つまり、この next は「非同期処理」というやつ。

Deferred.next(function(){
  print(1)
})
print(2)

// 2
// 1

割り込み可能。


チェーンの構築

Deferred.next がグローバル領域にあるとする。

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

こうやって繋げる。最初のスライドにもあるように、この時点では func1 と func2 は非同期ではない。

2つの next は同じように見えるけど、まったく違うものだった。

最初の next の部分は上に書いたように、Deferred クラスの新しいインスタンスを返す。なので、次の next は Deferred.prototype.next のほうになる。


ここで JSDeferred の頭の部分を見る。とりあえず全部無視して、問題の Deferred.prototype.next だけ見る。

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;
	}
};

Deferred.prototype.next は、Deferred.prototype._post を呼んでいる。Deferred.prototype._post は、 Deferred クラスの新しいインスタンスを作って callback.ok に引数の関数を入れて、それを返す。

とりあえずここまででやっていることを考える。

  • 最初の next で Deferred インスタンス (便宜的に d1 と呼ぶことにする) を作り、d1.callback.ok に func1 を入れた。
  • 次の next でまた Deferred インスタンス (便宜的に d2 と呼ぶことにする) を作り、d2.callback.ok に func2 を入れた。
  • また、d1._next に d2 をセットした。
  • 最後に setTimeout 0ms で d1.call() を実行。

これが next チェーンの解釈。図にするとこうなる。

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

この時点では d1.call() は非同期なので、チェーン構築の直後に書いた処理が割り込む可能性がある。


チェーンの実行

ここで (ようやく) Deferred.prototype.call を見る。内部的には Deferred.prototype._fire を呼んでるだけ。

Deferred.prototype._fire 内の this は d1 であることに注意しつつ見ていくと、まず

value = this.callback[okng].call(this, value);
// ここでは d1.callback.ok.call(d1,null) と同じ。
// つまり func1.call(d1,null) と同じ。
// つまり d1.func1() と同じ。

で func1 を実行する。(fun1 を書くときに、this は Deferred インスタンスとして書けばよかったんだね)

func1 が正常終了し、結果が null だとすると、次に実行されるのは

if (this._next) this._next._fire(next, value);
// つまり d2._fire('ok',null) と同じ。
// つまり d2.call() と同じ。

this._next (= d2) が存在する場合は d2.call() を実行。

d2._next は undefined なので、次は単に func2 を実行して終了。


func1 と func2 が同期的である理由がわかった。ここでは setTimeout を挟んでないから。


次回は

こっちだったら func1 と func2 は非同期実行となる。

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

また、このように書くと、

next(function func1(){
  /* 処理 */
  throw 'error'
})
.next(function func2(){
  /* 処理 */
})
.error(function func3(){
  /* 処理 */
})

func2 は実行されずに func3 (以降) が実行される。

次回はその意味を書いていく。と思う。飽きてなければ。


追記

総括も書いたよ。

 |