Latest topics > JavaScriptでsleepしたい、を実現する方法(require JavaScript 1.7)
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
JavaScriptでsleepしたい、を実現する方法(require JavaScript 1.7) - Feb 20, 2009
中野さんが、JavaScriptにはsleep(一定時間待ってから次の処理に進むという命令文)が無いせいでテストを書くのに難儀したという話を書かれているけれども。まさにこれをどうにかしたくて、UxUは進化してきたようなものと言える。
知ってる人は知ってるだろうけど、Firefox/Thunderbirdアドオン向けの自動テスト実行ツール・UxUでは、テストを書く上でsleepに相当する機能を利用できる。これは、JavaScript 1.7でジェネレータ・イテレータ機能を実現するために追加されたyield式のトリッキーな使い方だ。
これを実現しているのは、lib/utils.jsのdoIteration()
と、test/test_case.jsのrun()
ということになる。ここから要点だけを取り出すと、以下のような事をしている。
まず、スクリプトの書き手は、「sleepを使いたい処理」を関数として定義する。この時、sleep
の代わりにyield
を使う。
var task = function() {
doSomething1();
yield 1000;
doSomething2();
yield 1000;
doSomething3();
}
次に、スクリプトの書き手は、この関数を後述するdoIteration()
に渡す。すると、doIteration()
がよしなに計らって、「doSomething1()
を実行した後、1000ミリ秒待って、doSomething2()
を実行し、また1000ミリ秒待って、doSomething3()
を実行する」という風な形で先程の関数の内容を実行する。
この時のdoIteration()
の内容は、以下のような感じ。
function doIteration(aTask) {
if (typeof aTask == 'function') {
// 渡されたのが関数だったら、まず、評価した返り値を得る。
aTask = aTask();
}
if (!aTask ||
!('next' in aObject) ||
!('send' in aObject) ||
!('throw' in aObject) ||
!('close' in aObject) ||
aObject != '[object Generator]') {
// 渡されたオブジェクトまたは関数の返り値が
// ジェネレータ・イテレータではない場合、何もしない。
return;
}
// ここからがミソ!
// 全部の処理が終わったかどうか、を示すオブジェクトを定義
var finishFlag = { value : false, error : null };
var last = 0; // スリープ開始時点の時刻を保持する変数
var sleep = 0; // スリープの長さ(秒数)を保持する変数
var timer = window.setInterval(function() {
if (
// スリープの長さがちゃんと指定されていて
sleep > 0 &&
// スリープ開始時点からの経過時間がスリープとして
// 指定された時間未満であれば
(Date.now() - last) < sleep
) {
// ここで処理を終えて、100ミリ秒後まで待つ。
return;
}
// スリープとして指定された時間が経過したので、処理を進める。
try {
// 次にyieldが登場するまでの間の処理を実行。
sleep = aTask.next();
// next()の返り値はyield式に渡された値。
last = Date.now(); // スリープ開始時刻を保持して
return; // 処理を一旦終えて100ミリ秒後を待つ。
}
catch(e if e instanceof StopIteration) {
// 最後のyield式の後の内容が実行されて、定義された関数の内容が
// 最後まですべて実行されると、StopIteration例外が発生する。
// よって、処理完了とみなす。
finishFlag.value = true;
}
catch(e) {
// それ以外の未知の例外が発生した時は、処理中断とする。
finishFlag.error = e;
}
// 100ミリ秒ごとの繰り返し処理を停止。
window.clearInterval(timer);
}, 100);
return finishFlag;
}
UxUの内部でやってる事は基本的にはこういう事。ただ、実際にはもうちょっと使い勝手をよくするために細かい処理が加わってる。
doIteration()
が中で何をやってるのかを知らなければ、パッと見は、sleepという命令文の名前がyieldに変わっただけのようにすら見えるんじゃないだろうか。そこが、このやり方の狙いだ。スクリプトの書き手はタイムアウトだのコールバックだのといった難しい事を何も考えなくても良くて、単に「sleep文に相当する機能が加わったJavaScript」として好きなように処理を書く事ができる。テストを書くための工数が大幅に削減される(かもしれない)ので、テストを書くのが苦にならず、ばりばりテストを書けるようになる(はず)。その結果、充実したテストのおかげでより安心して開発に専念できるようになる(はず)。という理屈です。
ちなみに、勘のいい人は気付くだろうけど、setInterval()
に渡している関数の冒頭に以下の内容を挿入すれば、doIteration()
をいくらでも入れ子にできるようになる。
if (
typeof sleep == 'object' &&
(!sleep.value || !sleep.error)
) {
return;
}
var task = function() {
doSomething1();
yield 1000;
yield doIteration(function() {
doSomething2();
yield 500;
doSomething3();
yield doIteration(function() {
doSomething4();
yield 100;
doSomething5();
});
});
doSomething6();
};
doIteration(task);
さらに、こんなこともできる。
var task = function() {
doSomething1();
yield doIteration(function() {
var flag = { value : false; }
frame.addEventListener('load', function() {
frame.removeEventListener('load', arguments.callee, false);
flag.value = true;
}, true);
frame.contentDocument
.defaultView
.location.href = 'http://www.example.com/';
yield flag;
// フレームの読み込みが終わったらここに進む
doSomething2();
});
doSomething3();
};
doIteration(task);
この辺をもっと簡単に書けるようにヘルパーメソッドを色々と整備したのがUxUのテスト実行環境、と思ってもらえれば大体それで正解です。
25日追記。他にも色々やり方があるようです。(どっちもMozilla限定だけど)
このカテゴリ以下の他のエントリ
Comments/Trackbacks
TrackBack ping me at
の末尾に2020年11月30日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2009-02-20_sleep.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。
Post a comment
writeback message: Ready to post a comment.
カテゴリ一覧
- 全てのエントリ (2478)
- blosxom (9)
- チェックリスト (9)
- dream (1)
- イベント (71)
- 生活 (154)
- その他 (9)
- モテ・非モテ・恋愛・自己承認 (160)
- Mozilla (791)
- 拡張機能 (305)
- backtoowner (2)
- bfthumbnail (3)
- bookmarks2pane (3)
- ctxextensions (2)
- foxsplitter (4)
- greasemonkey (1)
- gsuggest (4)
- historycounter (1)
- informationaltab (2)
- multipletab (6)
- mystickies (1)
- observelipboard (1)
- openbookmarkintab (1)
- rewindforward (4)
- rubysupport (2)
- searchcache (4)
- secondsearch (5)
- splitbrowser (7)
- tabcatalog (2)
- tabextensions (2)
- tabkiller (1)
- textlink (11)
- textshadow (7)
- treestyletab (57)
- undotab (1)
- uxu (8)
- viewsourceintab (1)
- xulmigemo (50)
- extensions (1)
- treestyletab (1)
- fennec (3)
- Firefox (77)
- jetpack (2)
- その他 (135)
- Thunderbird (10)
- work (1)
- XUL (257)
- extensions (1)
- textlink (1)
- treestyletab (1)
- 拡張機能 (305)
- 絵 (63)
- moezilla (7)
- 立体物 (24)
- レビュー・感想 (205)
- 風景 (9)
- ソフトウェア (36)
- chrome (1)
- gimp (1)
- illustrator (1)
- inkscape (1)
- nsis (3)
- OpenOffice.org (1)
- safari (1)
- sai (2)
- システム再構築 (73)
- 出来事・雑感 (766)
- トリビア・ムダ知識 (22)
- 服装とか (6)
- Web技術 (69)
- CSS (3)
- JavaScript (31)
- jsdeferred (1)
- prototype.js (3)
- ruby (2)
- XML (1)
最近のコメント
- Windows 10で「デスクトップの解像度」と「アクティブな信号解像度」が一致しない現象が発生したときの解決方法
- 11/22 16:44 by えいぎょー
- 06/15 21:20 by えあ
- 06/17 21:22 by no name
- 言葉が通じない同士で対話するのが面白い「ヘテロゲニア リンギスティコ」、言葉は通じるのに対話ができないのが辛い「魚頭さんと袋さん」
- 03/21 17:50 by マチカ
- 表現の自由と表現規制に関する自分のスタンスの再確認
- 05/02 11:37 by れっか
- 05/02 20:20 by Piro
- 11/29 09:39 by p
- 激しくDISられた時は「この人は視野狭窄に陥っているオタクなんだ」と思うと辛くなくなる、というライフハック
- 08/18 10:21 by みら
- 08/18 21:28 by lovepotion
- 11/29 09:09 by p
- WSLでフォルダーを作るとWindowsでNTFSなパーティションでも大文字小文字が区別される落とし穴がある
- 12/30 21:25 by no name
- JPOP王道進行とウンコ曲と「違いが分かる人」
- 10/20 20:08 from yamj diary
- Gmail宛にメールを片っ端から転送すると元のアドレスのドメインが受信拒否されるようになることがある、という問題
- 05/18 18:43 from ひがきの日記
- 自信ありそうげな人が妬ましいし羨ましい
- 12/10 12:45 from 自分、無粋ですから
- 「元のソフトウェアがGPLだから公開できない」という誤解について
- 02/04 08:40 from 練炭ブログ
- Ubuntu 9.10からUbuntu 10.04にアップグレードした
- 08/10 12:39 from 日々想うこと