for 文で 100 項目とか 1000 項目とかあるテストケースを処理するとブラウザが固まる。
ということで for 文を setTimeout や setInterval に変換する事で定期的にブラウザに処理を戻すことができる。
// ここでは console.log のところでログを取ってますが // 通常は処理が入ります。 for (var i = 0; i < 3; i ++) { console.log('a' + i); } /* * 結果 * a0 * a1 * a2 */
これをまず while 文に変換
var i = 0; while (true) { if (!(i < 3)) break; console.log('a' + i); i ++; } /* * 結果 * a0 * a1 * a2 */
で、 setTimeout に変換
var i = 0; setTimeout(function a() { if (!(i < 3)) return; console.log('a' + i); i ++; setTimeout(a); }, 10); /* * 結果 * a0 * a1 * a2 */
これってアトミック?
つまり、 for 文が何個ネストしてても変換できる?
今度はネストした for 文
for (var i = 0; i < 3; i ++) { console.log('a' + i); for (var j = 0; j < 3; j ++) { console.log('b' + i + j); } } /* * 結果 * a0 * b00 * b01 * b02 * a1 * b10 * b11 * b12 * a2 * b20 * b21 * b22 */
さっきの方法で変換
var i = 0; setTimeout(function a() { if (!(i < 3)) return; console.log('a' + i); var j = 0; setTimeout(function b() { if (!(j < 3)) return; console.log('b' + i + j); j ++; setTimeout(b); }, 10); i ++; setTimeout(a); }, 10); /* * 結果(失敗してますね>< * a0 * b10 * a1 * b21 * b20 * a2 * b32 * b31 * b30 * b32 * b31 * b32 */
何故、失敗したのか
外側の setTimeout が先にガーッと終わっちゃってあとから 内側が実行されるから。(ワーカースレッドのキューを想像してください
じゃあ、こう変換すればいいんじゃね?
var i = 0; setTimeout(function a() { if (!(i < 3)) return; console.log('a' + i); var j = 0; setTimeout(function b() { if (!(j < 3)) return setTimeout(a); // ここで外側の setTimeout を呼ぶ console.log('b' + i + j); j ++; setTimeout(b); }, 10); i ++; }, 10); /* * 結果 * a0 * b00 * b01 * b02 * a1 * b10 * b11 * b12 * a2 * b20 * b21 * b22 */
うまくいったけど、この変換はうまくいかないんじゃない
for (var i = 0; i < 3; i ++) { console.log('a' + i); for (var j = 0; j < 3; j ++) { console.log('b' + i + j); } for (var k = 0; k < 3; k ++) { console.log('c' + i + k); } } // めんどくさくなったので以下結果省略
た、たしかし><
var i = 0; setTimeout(function a() { if (!(i < 3)) return; console.log('a' + i); var j = 0; setTimeout(function b() { if (!(j < 3)) return setTimeout(a); console.log('b' + i + j); j ++; setTimeout(b); }, 10); var k = 0; setTimeout(function c() { if (!(k < 3)) return setTimeout(a); console.log('c' + i + k); k ++; setTimeout(c); }, 10); i ++; }, 10);
ここで戦略を練る
- 単純に二つのループがネストした例なら、内側の setTimeout を呼ぶ必要が無くなったら、外側の setTimeout を呼べば良かった。
- しかし、内側のループが二つある場合。
- a は b の setTimeout を呼んで
- b は c の setTimeout を呼んで
- c は a の setTimeout を呼ぶ必要がある
- ということは setTimeout を呼ぶ必要がなくなったら「次の処理を進ませる」ための関数を実行すればいいんじゃないか。
こうか
var i = 0; setTimeout(function a() { if (!(i < 3)) return; console.log('a' + i); var j = 0; var b = function() { if (!(j < 3)) return b.next(); console.log('b' + i + j); j ++; setTimeout(b); }; setTimeout(b); b.next = function() { var k = 0; var c = function() { if (!(k < 3)) return c.next(); console.log('c' + i + k); k ++; setTimeout(c); }; setTimeout(c); c.next = function() { i ++; setTimeout(a); }; }; }, 10);
next とかがめんどくさいので setTimeout に引数を渡せるようにする関数を作る(Firefox は元々渡せるけど)
// こういう関数を作った var to = function() { var f = Array.prototype.shift.apply(arguments); args = arguments; return setTimeout(function() { f.apply(null, args) }, 10); }; var i = 0; to(function a(fin) { if (!(i < 3)) return fin(); console.log('a' + i); var j = 0; to(function b(fin) { if (!(j < 3)) return fin(); console.log('b' + i + j); j ++; to(b, fin); }, function() { var k = 0; to(function c(fin) { if (!(k < 3)) return fin(); console.log('c' + i + k); k ++; to(c, fin); }, function() { i ++; to(a, fin); }); }); }, function() { });
できた!
わーわーぱちぱち
というわけでおさらい
以下のような for 文があったら
for (var i = 0; i < 3; i ++) { // 処理 1 } // 処理 2
こんな感じの setTimeout を改造した関数をつくる
// ↓これ var to = function() { var f = Array.prototype.shift.apply(arguments); args = arguments; return setTimeout(function() { f.apply(null, args) }, 10); }; for (var i = 0; i < 3; i ++) { // 処理 1 } // 処理 2
for を以下のような while にする
var to = function() { var f = Array.prototype.shift.apply(arguments); args = arguments; return setTimeout(function() { f.apply(null, args) }, 10); }; // ↓こんな while var i = 0; while (true) { if (!(i < 3)) break; // 処理 1 i ++; } // 処理 2
でこうする
var to = function() { var f = Array.prototype.shift.apply(arguments); args = arguments; return setTimeout(function() { f.apply(null, args) }, 10); }; var i = 0; to (function f(fin) { if (!(i < 3)) return fin(); // 処理 1 i ++; to (f, fin); }, function() { // 処理 2 });
まとめ
ね?簡単でしょう?(ボブ風に