Window: setTimeout() メソッド
Baseline Widely available *
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
* Some parts of this feature may have varying levels of support.
setTimeout()
は Window
インターフェイスのメソッドで、時間切れになると、関数または指定されたコードの断片を実行するタイマーを設定します。
構文
setTimeout(code)
setTimeout(code, delay)
setTimeout(functionRef)
setTimeout(functionRef, delay)
setTimeout(functionRef, delay, param1)
setTimeout(functionRef, delay, param1, param2)
setTimeout(functionRef, delay, param1, param2, /* …, */ paramN)
引数
functionRef
-
タイマーが満了した後に実行する
関数
。 code
-
関数の代わりに文字列を含める代替構文も許容されており、タイマーが満了したときに文字列をコンパイルして実行します。
eval()
の使用にリスクがあるのと同じ理由で、この構文は推奨しません。 delay
省略可-
指定した関数やコードを実行する前に待つタイマーの時間をミリ秒 (1/1000 秒) 単位で指定します。この引数を省略すると値 0 を使用しますので「直ちに」実行する、より正確に言えばできるだけ早く実行することを意味します。
なお、どちらの場合も、実際の待ち時間が想定より長くなることがあります。後述する待ち時間が指定値より長い理由をご覧ください。
また、値が数値でない場合、暗黙のうちに型変換が行われ、数値に変換されることにも注意してください。これは予期しない、驚くべき結果につながる可能性があります。例として、delay の値が数値でない場合は暗黙に数値に強制されるを参照してください。
param1
, …,paramN
省略可-
タイマーが満了したときに、
functionRef
で指定された関数に渡す追加の引数です。
返値
setTimeout()
メソッドは、呼び出しによって作成されたタイマーを一意に識別する正の整数(通常は 1 から 2,147,483,647 の範囲)を返します。この識別子は、よく「タイムアウト ID」と呼ばれ、 clearTimeout()
に渡すことで、タイマーを停止することができます。
同じグローバル環境(特定のウィンドウやワーカーなど)では、元のタイマーがアクティブである限り、タイムアウト ID は確実に一意となり、新しいタイマーには再利用されません。ただし、グローバル環境が異なると、それぞれ独立したタイマー ID のプールが管理されます。
解説
タイムアウトは、Window.clearTimeout()
を使用して取り消すことができます。
関数を繰り返して(例えば N ミリ秒ごとに)呼び出すには、 setInterval()
を使用することを検討してください。
delay の値が数値でない場合は暗黙に数値に強制される
もし setTimeout()
が呼び出されたときの delay 値が数値でなかった場合、暗黙のうちに型変換が行われ、その値を数値に変換します。例えば、以下のコードは delay の値として、数値 1000
ではなく文字列 "1000"
を使用しています。しかし、コードが実行されると文字列が数値 1000
に強制されるため、どのみち動作し、 1 秒後にコードが実行されます。
setTimeout(() => {
console.log("1 秒待ちました。");
}, "1000");
しかし、多くの場合、暗黙の型強制は予期しない、驚くべき結果をもたらす可能性があります。例えば、以下のコードを実行すると、文字列 "1 second"
は最終的に数字 0
に変換され、その結果、コードは待ち時間ゼロで直ちに実行されます。
setTimeout(() => {
console.log("1 秒待ちました。");
}, "1 second");
したがって、 delay の値には文字列を使用せず、常に数字を使用してください。
setTimeout(() => {
console.log("1 秒待ちました。");
}, 1000);
非同期関数の動作
setTimeout()
は非同期関数です。これは、タイマー関数は関数スタック内の他の関数の実行を停止させないということです。
言い換えると、 setTimeout()
を使って、関数スタックの次の関数が起動するまでの「間」を作ることはできません。
以下の例をご覧ください。
setTimeout(() => {
console.log("これは最初のメッセージです");
}, 5000);
setTimeout(() => {
console.log("これは 2 番目のメッセージです");
}, 3000);
setTimeout(() => {
console.log("これは 3 番目のメッセージです");
}, 1000);
// 出力:
// これは 3 番目のメッセージです
// これは 2 番目のメッセージです
// これは最初のメッセージです
最初の関数は、 2 番目の関数を呼び出す前に 5 秒間の「間」を作らないことに注意してください。その代わり、 1 番目の関数が呼び出されますが、実行されるまで 5 秒間待機します。 1 番目の関数が実行を待っている間に 2 番目の関数が呼び出され、 2 番目の関数が実行される前に 3 秒の待ち時間が適用されます。 1 番目の関数も 2 番目の関数もタイマーが終了していないので、 3 番目の関数が呼び出され、先に実行を完了します。その後、 2 番目の関数が続きます。そして、最後に 1 番目の関数のタイマーが終了した後、 1 番目の関数が実行されます。
ある関数が実行された後に別の関数が実行されるような処理を行うには、プロミスのドキュメントを参照してください。
"this" の問題
setTimeout()
にメソッドを渡すと、 this
が期待とは異なる値で起動されることがあります。一般的な問題は JavaScript リファレンスで詳細に説明されています。
setTimeout()
によって実行されるコードは、setTimeout
が呼び出された関数とは別の実行コンテキストから呼び出されます。呼び出された関数で this
キーワードを設定する際の通常のルールが適用され、this
を呼び出し時に設定していない場合、または bind
で設定していない場合、window
(または global
)オブジェクトが既定で使用されます。これは、厳格モードであっても同様です。これは、setTimeout
を呼び出した関数の this
の値と同じではありません。
以下の例をご覧ください。
const myArray = ["zero", "one", "two"];
myArray.myMethod = function (sProperty) {
console.log(arguments.length > 0 ? this[sProperty] : this);
};
myArray.myMethod(); // "zero,one,two" と表示
myArray.myMethod(1); // "one" と表示
myMethod
を呼び出したときに、呼び出しによって this
が myArray
に設定されますので、関数内で this[sProperty]
は myArray[sProperty]
と等価です。しかし、以下のコードでは動作が異なります。
setTimeout(myArray.myMethod, 1.0 * 1000); // "[object Window]" と 1 秒後に表示
setTimeout(myArray.myMethod, 1.5 * 1000, "1"); // "undefined" と 1.5 秒後に表示
myArray.myMethod
関数を setTimeout
に渡しており、関数が呼び出されると this
が前のように設定されず、既定の window
オブジェクトになります。
Array の forEach()
や reduce()
などのメソッドにあるような、thisArg
を setTimeout
に渡すオプションもありません。また以下のように、this
を設定するために call
を使用する方法も動作しません。
setTimeout.call(myArray, myArray.myMethod, 2.0 * 1000); // エラー
setTimeout.call(myArray, myArray.myMethod, 2.5 * 1000, 2); // 同じエラー
解決策
ラッパー関数の使用
この問題の一般的な解決策は、this
に必要な値を設定するラッパー関数を使用することです。
setTimeout(function () {
myArray.myMethod();
}, 2.0 * 1000); // "zero,one,two" と 2 秒後に表示
setTimeout(function () {
myArray.myMethod("1");
}, 2.5 * 1000); // "one" と 2.5 秒後に表示
代わりにアロー関数も使用することができます。
setTimeout(() => {
myArray.myMethod();
}, 2.0 * 1000); // "zero,one,two" と 2 秒後に表示
setTimeout(() => {
myArray.myMethod("1");
}, 2.5 * 1000); // "one" と 2.5 秒後に表示
bind() の使用
他に、 bind()
を使用して this
の値をその関数のすべての呼び出しに設定することができます。
const myArray = ["zero", "one", "two"];
const myBoundMethod = function (sProperty) {
console.log(arguments.length > 0 ? this[sProperty] : this);
}.bind(myArray);
myBoundMethod(); // "zero,one,two" と表示。関数内で 'this' が myArray に結び付けられているため。
myBoundMethod(1); // "one" と表示
setTimeout(myBoundMethod, 1.0 * 1000); // こちらも結びつけがあるため "zero,one,two" と 1 秒後に表示
setTimeout(myBoundMethod, 1.5 * 1000, "1"); // "one" と 1.5 秒後に表示
文字列リテラルの解釈
関数の代わりに文字列を setTimeout()
に渡すと、eval()
を使うのと同様の問題が発生します。
// こうやってはいけない
setTimeout("console.log('Hello World!');", 500);
// こうすればよい
setTimeout(() => {
console.log("Hello World!");
}, 500);
setTimeout()
に渡した文字列はグローバルコンテキストで評価されます。そのため、setTimeout()
が呼び出されたコンテキストのローカルシンボルは、文字列を評価したコードからは利用できません。
待ち時間が指定値より長い理由
タイムアウトが満了するまでに予想より長い時間がかかる理由は複数あります。この節では、もっとも一般的な理由を説明します。
入れ子のタイムアウト
HTML 標準で指定されているとおり、ブラウザーは setTimeout
の入れ子になった呼び出しが 5 回スケジュールされると、最小 4 ミリ秒のタイムアウトを強制します。
この例では、 setTimeout
の呼び出しを 0
ミリ秒の待ち時間でネストし、ハンドラーが呼び出されるたびに待ち時間時間を記録しています。最初の 4 回は待ち時間が約 0 ミリ秒、その後は約 4 ミリ秒になります。
<button id="run">実行</button>
<table>
<thead>
<tr>
<th>前回</th>
<th>今回</th>
<th>実際の待ち時間</th>
</tr>
</thead>
<tbody id="log"></tbody>
</table>
let last = 0;
let iterations = 10;
function timeout() {
// この呼び出しの時刻をログ出力
logline(new Date().getMilliseconds());
// まだ終わっていない場合は、次の呼び出しをスケジュール
if (iterations-- > 0) {
setTimeout(timeout, 0);
}
}
function run() {
// clear the log
const log = document.querySelector("#log");
while (log.lastElementChild) {
log.removeChild(log.lastElementChild);
}
// 反復処理の回数と開始タイムスタンプを初期化
iterations = 10;
last = new Date().getMilliseconds();
// start timer
setTimeout(timeout, 0);
}
function logline(now) {
// 最後のタイムスタンプ、新しいタイムスタンプ、および差分をログ出力
const tableBody = document.getElementById("log");
const logRow = tableBody.insertRow();
logRow.insertCell().textContent = last;
logRow.insertCell().textContent = now;
logRow.insertCell().textContent = now - last;
last = now;
}
document.querySelector("#run").addEventListener("click", run);
アクティブでないタブのタイムアウト
バックグラウンドのタブによる負荷(および関連するバッテリーの使用量)を軽減するために、ブラウザーはアクティブでないタブの最小タイムアウト時間を強制します。また、ページがウェブオーディオ API の AudioContext
を使用して音声を再生している場合、このタイムアウトが免除されることもあります。
この仕様はブラウザーに依存します。
-
Firefox のデスクトップ版と Chrome では、アクティブでないタブの最小タイムアウトは 1 秒です。
-
Android 版 Firefox では、アクティブでないタブのタイムアウトは最低 15 分で、タブを完全にアンロードする可能性もあります。
-
Firefox は、タブに
AudioContext
が含まれている場合、アクティブでないタブをスロットルで処理しません。 -
Chrome は、タブのアクティブ状況に応じて、さまざまなレベルのスロットル処理を使用します。
-
最小スロットル処理: ページが表示されている、最近音を発した、または Chrome によってアクティブとみなされたタイマーに適用されます。タイマーは、リクエストされた間隔に近いタイミングで実行されます。
-
スロットル処理: 最小スロットル条件が満たされておらず、以下の条件のいずれかが真の場合にタイマーに適用されます。
- 入れ子数 (つまり、連鎖したタイマーの呼び出しの数) が 5 未満である。
- ページが表示されなくなってから 5 分以内。
- WebRTC がアクティブである。
この状態のタイマーは 1 秒ごとに 1 回チェックされます。このチェックは、同様のタイムアウトを持つ他のタイマーとまとめてバッチ処理される場合があります。
- 集中的なスロットル処理: Chrome 88(2021 年 1 月)で導入されました。最小スロットル処理もスロットル処理の条件も満たされておらず、次の条件がすべて満たされている場合に、タイマーに適用されます。
- 入れ子数が 5 以上。
- ページが表示されなくなってから 5 分以上経過している。
- ページが 30 秒以上無操作である。
- WebRTC がアクティブではない。
この状態のタイマーは 1 分に 1 回チェックされ、同様のタイムアウトを持つ他のタイマーとまとめて処理される場合があります。
-
トラッキングスクリプトのタイムアウトを制限する
Firefox は、トラッキングスクリプトとして認識されたスクリプトに対して追加のスロットルを適用します。 フォアグラウンドで実行されている場合、最小待ち時間は 4ms のままです。しかし、バックグラウンドのタブでは、最小待ち時間時間は 10,000ms (10 秒)で、文書が最初に読み込まれてから 30 秒後に有効になります。
詳しくは、トラッキング保護を参照してください。
タイムアウトの待ち時間
ページ(または OS やブラウザー)が他のタスクでビジー状態場合、タイムアウトが予想より遅れて発生することがあります。
注意すべき重要なケースとして、 setTimeout()
を呼び出したスレッドが終了するまで、関数やコードスニペットを実行することができないことがあります。例えば、
function foo() {
console.log("foo has been called");
}
setTimeout(foo, 0);
console.log("After setTimeout");
このコードは、コンソールへ以下のように出力します。
After setTimeout foo has been called
これは setTimeout
を待ち時間 0 で呼び出したとしても、直ちに実行するのではなくキューに載せて、次の機会に実行するようスケジューリングされるためです。現在実行中のコードはキューにある関数を実行する前に完了しなければならず、このために実行結果の順序が想定どおりにならない場合があります。
ページロード中のタイムアウトの待ち時間
Firefox は現在のタブがロードされている間、 setTimeout()
タイマーの発行を延期します。メインスレッドがアイドルと判断されるまで(Window.requestIdleCallback()
と同様)、または load イベントが発生するまで起動が延期されます。
WebExtension のバックグラウンドページとタイマー
WebExtension では、 setTimeout()
は信頼できる動作をしません。拡張機能の作者は、代わりに alarms
API を使用してください。
最大の待ち時間時間
ブラウザーは待ち時間時間を内部的に 32 ビット符号付き整数として格納するため、 2,147,483,647 ミリ秒(約 24.8 日)を超える待ち時間を使用すると、整数オーバーフローが発生します。例えば、次のコードでは、
setTimeout(() => console.log("hi!"), 2 ** 32 - 5000);
…タイムアウトが即座に実行される結果となります(2**32 - 5000
が負の数にオーバーフローするため)。一方、次のコードのようにすると、
setTimeout(() => console.log("hi!"), 2 ** 32 + 5000);
…タイムアウトは約 5 秒後に実行されます。
メモ: これは、Node.js の setTimeout
の動作と一致しません。Node.js では、2,147,483,647 ミリ秒を超えるタイムアウトは即座に実行されます。
例
タイムアウトの設定と取り消し
以下の例はウェブページに 2 つのシンプルなボタンを置いており、setTimeout()
および clearTimeout()
のルーチンを実行します。1 番目のボタンを押下すると 2 秒後にアラートダイアログを呼び出すタイムアウトを設定して、clearTimeout()
で使用するタイムアウト ID を保存します。2 番目のボタンを押下すると、このタイムアウトをキャンセルできます。
HTML
<button onclick="delayedMessage();">2 秒後にアラートボックスを表示</button>
<button onclick="clearMessage();">アラート発生前に取り消し</button>
<div id="output"></div>
JavaScript
let timeoutID;
function setOutput(outputContent) {
document.querySelector("#output").textContent = outputContent;
}
function delayedMessage() {
setOutput("");
timeoutID = setTimeout(setOutput, 2 * 1000, "本当に遅い!");
}
function clearMessage() {
clearTimeout(timeoutID);
}
結果
clearTimeout()
の例も参照してください。
仕様書
Specification |
---|
HTML # dom-settimeout-dev |