JavaScriptの setTimeout関数の実行タイミングに関する誤解

JavaScriptの「setTimeout」は、(ミリ秒で)指定した時間後に、指定した関数を実行してくれるのですが、実は実行タイミングがややこしい!のです。私も誤解していました。


例えば、下記を実行すると、どうなるでしょうか?

document.write("hoge\n");
setTimeout(function(){ document.write("fuga\n") }, 1000);
document.write("piyo\n");


実行結果は、下記の引用の通り、setTimeout関数で指定した「fuga」という文字列が最後に表示されます。

普通に JavaScript を使いこなしてる人なら、hoge → piyo と表示して、 1 秒後に fuga が表示されるな。って思うはずなんです。
でも、 JavaScript を始めたばっかりの人の中には、 hoge と表示したあと 1 秒後に fuga → piyo と表示するな。って思ってる人が非常に多い。(経験的に)

JavaScript を学ぶ際に一番重要なのに、誤解されがちな setTimeout 系の概念 - IT戦記

タイマーのカウントダウンは「一連の処理が終わってから」

つまり、setTimeoutで指定した時間がカウントダウンされるのは、「一連の処理が終わってから」であり、「setTimeout の行を通過する時ではない」ということです。この「一連の処理」について確認するために、以下のコードを実行してみます。

function timer(){
    alert("aaa");
	setTimeout( function() {alert("bbb");}, 5000);
	alert("ccc");
}
function main(){
    alert("ddd");
	timer();
	alert("eee");
}
main();


実行結果は下記のようになり、特に「eee が表示されてから、5秒後に bbb が表示」されることがポイントです。ここでは、「一連の処理」=「mainメソッド⇒timerメソッド」となり、その一連の処理が完了してから指定時間(ここでは5秒)をカウントしていることが分かります。

ddd
aaa
ccc
eee
〜〜〜 ここで5秒間の待ちが発生 〜〜〜
bbb


この(実行タイミングの)特徴により、以下のことが保証されることになります。
setTimeoutで指定した関数が実行される時には、他の処理は完了している

補足1: ノンブロッキング

setTimeoutはノンブロッキング*1であるため、setTimeoutが実行された後、JavaScriptは結果を待たずに次の処理に取りかかることができます。

補足2: 極端に短い時間は指定できない

以下の引用にもあるとおり、あまりに短い時間(1msなど)で実行できてしまうと、クライアントPCに多大な負荷がかかってしまうため、ブラウザ側で最低ラインが設けられているようです。

マルチタスク・マルチウィンドウ・マルチタブ前提のパソコン用のブラウザーの場合、もしJavascriptを全力で実行してしまうと、このベンチマークのように最小の遅延でスクリプトを実行するページを開いたとたんにブラウザー自身のレスポンスが極端に悪くなってしまう。
 そのために、たとえプログラマーJavascript側でSetTimeout()の遅延パラメータとして1msを指定したとしても、最低でも10msとか15.6msの遅延後にしかタイマー関数を呼ばない、という設計になっているのが普通だ

Life is beautiful: Javascript雑学:SetTimeoutについて知っておくべき事