Hatena::Groupjavascript

JavaScriptで遊ぶよ

 | 

2009-04-14

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

00:33

昨日まで。

今日で最後。


loopについて


その前に Deferred.call について

昨日「Deferred.next に入れた関数には引数を渡せない」と書いたが、引数を渡せるようにするのが Deferred.call というわけ。

Deferred.call = function (f, args) {
	args = Array.prototype.slice.call(arguments);
	f    = args.shift();
	return Deferred.next(function () {
		return f.apply(this, args);
	});
};

これを読めば分かるように、こういう処理になっている。

//↓これは
Deferred.call(print, 'hoge')

//↓これと同じ意味
Deferred.next( function(){ print.apply(this,'hoge') } )

単なる Deferred.next のラッパーだった。


loop の使い方

JSDeferred による非同期処理のスライドより。

処理を切り離したい 2
for (var i = 0; i < array.length; i ++) {
    // array[i] をどうこうする処理
}
処理を切り離したい 2
loop(array.length, function(i) {
    // array[i] をどうこうする処理

});

// loop 関数を使う

// まだこの時点では処理は切り離されない (引用者註 : これは違う!)

処理を切り離したい 2
loop(array.length, function(i) {
    // array[i] をどうこうする処理
    return wait(0);
});

// ここで wait 関数を呼び出す (引用者註 : これも必要ない!)

処理を切り離したい 2
loop({ begin: 0, end: array.length, step: 3 },
                                   function(n, o){
    for (var i = 0; i < o.step; i ++) {
        // array[n+i] をどうこうする処理
    }
    return wait(0);
});

// 3 回に一回だけ切り離したい場合は

loop の第一引数には、繰り返しの回数かハッシュを渡す。ハッシュの場合は必須の end とオプショナルである begin、step が使える。

註を付けた部分の解説は後で。


ソースを見る

Deferred.loop = function (n, fun) {
	var o = {
		begin : n.begin || 0,
		end   : (typeof n.end == "number") ? n.end : n - 1,
		step  : n.step  || 1,
		last  : false,
		prev  : null
	};
	var ret, step = o.step;
	return Deferred.next(function () {
		function _loop (i) {
			if (i <= o.end) {
				if ((i + step) > o.end) {
					o.last = true;
					o.step = o.end - i + 1;
				}
				o.prev = ret;
				ret = fun.call(this, i, o);
				if (ret instanceof Deferred) {
					return ret.next(function (r) {
						ret = r;
						return Deferred.call(_loop, i + step);
					});
				} else {
					return Deferred.call(_loop, i + step);
				}
			} else {
				return ret;
			}
		}
		return (o.begin <= o.end) ? Deferred.call(_loop, o.begin) : null;
	});
};

内部的にはこれも Deferred.next を作って返すラッパー。loop に渡した関数は fun という名前になっている。

最初の日に書いたように、Deferred.next に渡す関数内での this は、Deferred.next が返す Deferred インスタンス。 それを ret = fun.call(this, i, o); と呼んでいるので、loop に渡す関数でも this を Deferred インスタンスとして書けばいい。

fun の帰り値が Deferred インスタンスでない場合は、そのまま Deferred.call で次の処理を実行。fun の帰り値が Deferred インスタンスである場合は、そいつの next として次の処理を Deferred.call する関数をチェーンする。


処理は非同期

Deferred.call が Deferred.next のラッパーなので、最初の日に書いたように、ループの一つ一つは非同期で実行される。

なので、こういうことができる。(print 関数が定義されているとして)

Deferred.loop(10,function(i){
    print(i)
});
Deferred.loop(10,function(i){
    print(String.fromCharCode(i+97));
});

/* 結果 */
0
a
1
b
2
c
3
d
4
e
5
f
6
g
7
h
8
i
9
j
print('press any key to stop the loop');

var flag = false;
window.addEventListener('keydown',function(){
  print('key pressed');
  flag = true;
  window.removeEventListener('keydown',arguments.callee,false);
},false);

Deferred.loop(1000000,function(i){
  if(flag){
    print('terminating');
    throw 'error'
  }else{
    print(i)
  };
});

/* 結果 */
press any key to stop the loop
0
1
2
3
(中略)
3940
3941
3942
3943
key pressed
terminating


最後に

ここまでで JSDeferred のソースはほぼ全部読んだ。Deferred.next_faster_way_Image あたりの話だけ省略したけど、cho45 さんの記事↓を読めばわかると思う。


長くなったので aloop のことを書くのはやめた。

オレオレ JSDeferred プラグインを書くときは Deferred.parallel と Deferred.loop のソースを参考にすれば書き方はだいたいわかる。

Deferred.next に繋げるのがポイント。

さーて、JSDeferred で遊ぶとするか。


追記

総括も書いたよ。

司徒正美司徒正美2010/04/20 16:06 window.onload = function(){
var print = function(a){
var d = document.createElement("div");
d.innerHTML = a
document.body.appendChild(d)
}
Deferred.loop(10,function(i){
print(i)
});
Deferred.loop(10,function(i){
print(String.fromCharCode(i+97));
});
}
/* 結果 */
a
b
c
d
e
f
g
h
i
j
0
1
2
3
4
5
6
7
8
9

司徒正美司徒正美2010/04/20 16:08これはIE8の結果

edvakfedvakf2010/04/20 16:42Windows 持ってないので確かめられませんが、何十回か何百回かやれば混ざりあうと思いますよ。
Deferred.next が IE だけは他とは別の方法でやってるので、他と違っても不思議ではないと思います。
まったく混ざり合わなければ不思議ですが。

トラックバック - http://javascript.g.hatena.ne.jp/edvakf/20090414
 |