setInterval/setTimeout を使った非同期ループ

以下のような巨大なループを実行するとブラウザがフリーズする。

for (var i = 0; i < 100000; i++) {
//...
}
alert("done");

フリーズを回避するには、for ループを、setInterval/setTimeout に置き換えればよい。Flash ならば onEnterFrame イベントも有用。 ここでは、setInterval/setTimeout を使ったループを、非同期ループと呼ぶ。

実装すると以下のようになる。「非同期ループ処理 (7) - 同期非同期複合型」を参考に、幾つかの小さい同期ループに分割した。

非同期ループの実装例

function asynchronousLoop(from, to, obj, func, callback) {
var interval = 10;
var cellSize = 200;
var range = to - from;
var innerLoop = function(from, to) {
for (var i = from; i < to; i++) {
func.apply(obj, [i]);
}
}
if (range < cellSize) {
innerLoop(from, to);
callback.apply(obj);
} else {
var cellTotal = Math.ceil(range / cellSize);
var count = 0;
var intervalId = setInterval(function() {
var innerFrom = from + count * cellSize;
var innerTo = (from + (count + 1) * cellSize) < to ? (from + (count + 1) * cellSize) : to;
innerLoop(innerFrom, innerTo);
count++;
if (count >= cellTotal) {
clearInterval(intervalId);
callback.apply(obj);
}
}, interval);
}
}

冒頭の 100000 回のループを書き換えるとこうなる。

asynchronousLoop(0, 100000, this, function(i) {
//...
}, function() {
alert("done");
});

非同期ループの問題点

  • 別のスレッドになるため、単純に、既存の for ループを非同期ループに置き換えることができない。
  • 同様に、ネストすることが難しい。