2007-11-08
for 文を setTimeout に変換する
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 });
まとめ
ね?簡単でしょう?(ボブ風に
トラックバック - http://d.hatena.ne.jp/amachang/20071108/1194501306
- Re: for 文を setTimeout に変換する
- WEB開発日記 - for 文を setTimeout に変換する - IT戦記
- snippets from shinichitomita’s journal - ループをsetTimeoutで...
- llameradaの日記 - for 文を setTimeout に変換する(継続風)
- Webアプリを作ろう - 11/08 scrap
- kjのメモ - [テクニック]
- javascript - setTimeout()化とネスト
- [Django][Python][jQuery][CSS][その他]巡回
- SE的京都生活 - setTimeout関数やっと分かった!
- ITシンドローム - (HTML)スタイルシートを使ったプログレスバー
- IT戦記 - 動的スクリプトローディング(さんざん既出だと思うけど
- NyaRuRuの日記 - Volta 探検 (4): P/Invoke
リンク元
- 457 http://reader.livedoor.com/reader/
- 210 http://b.hatena.ne.jp/hotentry
- 138 http://b.hatena.ne.jp/entrylist?sort=hot
- 116 http://b.hatena.ne.jp/
- 105 http://www.google.com/reader/view/
- 104 http://d.hatena.ne.jp/
- 76 http://www.google.co.jp/reader/view/
- 70 http://blog.livedoor.jp/dankogai/archives/50946816.html
- 59 http://blog.livedoor.jp/dankogai/
- 53 http://www.google.co.jp/ig?hl=ja

