【JavaScript】 タイマー処理/setTimeoutとsetInterval
更新日:2023/02/06
プログラムコードを作成していると、特定の時間経過後に処理を実行したいことがあります。
JavaScriptではsetTimeoutとsetIntervalで実現できます。
この記事ではsetTimeoutとsetIntervalの使用方法および、知っておくべき注意点をお伝えします。
重要:JavaScriptのタイマー処理
setTimeoutとsetIntervalは指定した時間が経過した後に、指定した関数を実行する関数です。
例えば5秒経過後に何らかの処理をおこないたい場合などに、使用します。
ただし、正確に5秒後に実行されるわけではありません。
■なぜ正確ではないのか?
正確ではない理由は、タイマーがタスクとして処理されるからです。
JavaScriptにはイベントループという仕組みがあります。
イベントループはタスクキューを監視していて、キューにタスクが追加されると順番に処理していきます。
タイマーもタスクとして処理されます。
基本的には他のタスクよりも優先的に処理されますが、直前に実行しているコードの終了を待つ必要があります。
コードの終了まで時間がかかれば、それだけタイマーのコールバック呼び出しが遅くなります。
そのためJavaScriptは、正確な時間で呼び出されることを前提としたプログラミングをしてはいけないことに注意してください。
setTimeout:時間経過後1回だけ実行
一定時間経過後に処理を行いたいときは、setTimeoutメソッドを使用します。
構文
setTimeoutメソッドの構文
setTimeout( コールバック関数 );
setTimeout( コールバック関数 , ミリ秒 );
setTimeout( コールバック関数 , ミリ秒 , 引数1 , 引数2 ... );
引数
■コールバック関数
一定時間経過後に、JavaScriptのシステムによって呼び出される関数です。
システムから引き渡される引数はありませんが、setTimeoutメソッド呼び出し時に独自の値を指定することができます。
■ミリ秒
コールバック関数が呼び出されるまでの時間をミリ秒で指定します。
省略可能です。
省略された場合は0が指定されたとみなされます。
値は0から指定できますが、処理系によっては呼び出しまでの最短時間が制限されることがあります。
例えばブラウザ上で動作するJavaScriptの多くは4ms以上に制限されています。
またブラウザの非アクティブなタブ上での呼び出しは、1秒以上に制限されています。
■引数1 , 引数2 ...
省略可能です。
システムによるコールバック関数呼び出し時に、引数として渡されます。
戻り値
setTimeoutメソッドで登録されたタイマーイベントを識別するためのデータが返ります。
タイマーを停止させるために使用します。
この値は処理系により異なります。
例:
ブラウザ ... 整数値
node.js ... オブジェクト
使用例
一定時間経過後に処理をおこなう
次のコードは3秒後に経過時間を表示しています。
使用例1:3秒後に経過時間を表示
const startTime = Date.now();
setTimeout( function (){
console.log( Date.now() - startTime ); // 3008
},3000);
Date.now()を使用することで、経過時間を計測することができます。
詳しくは次のページを読んでみてください。
■【JavaScript】 経過時間や日数を取得し処理時間などを計測
一定時間ごとに処理をおこなう
次の処理は、コールバック関数内でsetTimeoutを呼び出すことで3秒毎に同じ処理をおこなっています。
3秒毎に処理をおこなう
const timeoutFunc = ()=>{
console.log( Date.now() - startTime );
setTimeout( timeoutFunc , 3000 );
};
const startTime = Date.now();
setTimeout( timeoutFunc , 3000 );
ここでの注意点は、setTimeoutメソッドを実行した時点が時間計測の開始位置となることです。
コールバック関数内でsetTimeoutメソッド以前の処理が何秒かかっても、関係ありません。
この後に紹介するsetIntervalは一定時間毎に繰り返し処理をおこなうメソッドですが、コールバック関数の開始が次のタイマー呼び出しの計測開始位置となります。
上のコードはtimeoutFunc関数内でtimeoutFunc関数を呼び出しているように見えます。
そのため再帰処理と混同することがあります。
実際にはsetTimeoutメソッドでtimeoutFunc関数をイベントキューに登録しているだけです。
timeoutFunc関数の呼び出しは、イベントループでおこなわれるので再帰ではありません。
setInterval:指定時間毎に繰り返し実行
一定時間毎に処理を繰り返したいときは、setIntervalメソッドを使用します。
構文
setIntervalメソッドの構文
setInterval( コールバック関数 , ミリ秒 );
setInterval( コールバック関数 , ミリ秒 , 引数1 , 引数2 ... );
引数
■コールバック関数
一定時間経過後に、JavaScriptのシステムによって呼び出される関数です。
システムから引き渡される引数はありませんが、setIntervalメソッド呼び出し時に独自の値を指定することができます。
■ミリ秒
コールバック関数が呼び出されるまでの時間をミリ秒で指定します。
値は0から指定できますが、処理系によっては呼び出しまでの最短時間が制限されることがあります。
例えばブラウザ上で動作するJavaScriptの多くは10ms以上に制限されています。
またブラウザの非アクティブなタブ上での呼び出しは、1秒以上に制限されています。
■引数1 , 引数2 ...
省略可能です。
システムによるコールバック関数呼び出し時に、引数として渡されます。
戻り値
setTimeoutメソッドで登録されたタイマーイベントを識別するためのデータが返ります。
タイマーを停止させるために使用します。
この値は処理系により異なります。
例:
ブラウザ ... 整数値
node.js ... オブジェクト
使用例
次のコードは3秒毎に経過時間を表示しています。
使用例1:3秒後に経過時間を表示
const startTime = Date.now();
setInterval( function (){
console.log( Date.now() - startTime );
},3000);
// 実行結果
// 3004
// 6016
// 9030
// 12044
// ・・・・
setIntervalはコールバック関数の開始が次のタイマー呼び出しの計測開始位置となります。
そのため、コールバック関数の処理時間が指定時間以上かかる場合、イベントループに入った時点で次のコールバック関数呼び出しがおこなわれます。
コールバック関数呼び出しの間隔をあけたい場合は、setIntervalではなくsetTimeoutメソッドを連続して呼び出してください。
clearTimeout/clearInterval:タイマーを中止する
setTimeoutまたはsetIntervalメソッドで登録したタイマーを中止するときは、clearTimeoutまたはclearIntervalメソッドを使用します。
構文
clearTimeoutおよびclearIntervalメソッドの構文
clearTimeout( タイマーID );
clearInterval( タイマーID );
引数
■タイマーID
setTimeoutまたはsetIntervalメソッドが返した値です。
setTimeoutが返したIDはclearTimeoutで、setInterval返したIDはclearIntervalで使用することが想定されますが、処理系によっては逆の組み合わせでもタイマーを停止することができます。
しかし想定通りの組み合わせで使用することを推奨します。
使用例
setIntervalの停止
setIntervalで登録したタイマーを、コールバック関数を5回呼び出した時点で停止させる例です。
setIntervalの停止
const startTime = Date.now();
let count = 0;
const timerID = setInterval( function (){
console.log( Date.now() - startTime );
if( ++count >= 5 ) clearInterval(timerID);
},3000);
setTimeoutの停止
setTimeoutで登録したタイマーが、コールバック関数を5回呼び出した時点で停止する例です。
setIntervalの停止
const timeoutFunc = count =>{
console.log( Date.now() - startTime );
count ++;
const timerId = setTimeout( timeoutFunc , 3000 , count);
if( count >= 5 ) clearTimeout(timerId);
};
const startTime = Date.now();
setTimeout( timeoutFunc , 3000 , 0);
上のコードでは呼び出しカウントをコールバック関数の引数として渡しています。
外部でカウント用の変数を用意するよりも、スマートですね。
場合によっては、startTimeも引数で渡すかどうか検討する余地があります。
なお上のコードは、ムダな処理をおこなっています。
setTimeoutの後にclearTimeoutを実行していますが、setTimeoutを実行しなければいいだけですね。
修正:setIntervalの停止
const timeoutFunc = count =>{
console.log( Date.now() - startTime );
if( ++count < 5 ) setTimeout( timeoutFunc , 3000 , count);
};
const startTime = Date.now();
setTimeout( timeoutFunc , 3000 , 0);
setTimeout/setIntervalが効かないときの確認点
今回の記事を書くにあたってタイマーについて調査をおこなっていたら、タイマーが効かなくて困っている初心者がいることがわかりました。
setTimeoutとsetIntervalは重要:JavaScriptのタイマー処理でお伝えしていますが、イベントの待機一覧に”呼び出し時間”と”コールバック関数”を登録しています。
この二つを上手く引数として渡すことができていれば、イベントループでタイマー呼び出しが行われます。
何が問題なのか
”呼び出し時間”が問題?
”呼び出し時間”については数値を指定すればいいので問題ないと思います。
仮に"3000"などの数値文字列を指定しても、内部で数値に変換してくれます。
また"abc"などの数値ではない文字列は、0として扱われるようです。
どちらにしても、コールバック関数の呼び出しがおこなわれます。
”呼び出し時間”の設定値は問題ではありません。
”コールバック関数”が問題?
問題は”コールバック関数”です。
タイマー呼び出しが効かないというケースは、次のコードの(2)のように関数に()を付けていることが多いようです。
const timerFunc = ()=>{
// 何らかの処理
};
setTimeout( timeoutFunc , 3000 ); // (1)
setTimeout( timeoutFunc() , 3000 ); // (2) タイマーが効かない!
(1)は、timerFunc変数が参照している関数オブジェクトを引数として渡しています。
渡された側は、()を付けることで関数を実行できます。
(2)はtimerFunc変数が参照している関数オブジェクトを実行して、その結果をsetTimeoutに渡しています。
実行した関数はreturn文がないので、結果はundefined値です。
undefined値は関数オブジェクトではないので、( )をつけても実行できません。
タイマーが効かないのは、これが理由です。
僕が初心者の頃同じ間違いをしていました。
その時の反省点を踏まえて、次のような記事を書いています。
■【JavaScript】 コールバックに関数を指定すると実行されてしまう理由
無限ループするsetTimeout
次のコードは、この記事内で紹介しているsetTimeoutでタイマー呼び出しを連続しておこなうコードを変更したものです。
const timeoutFunc = ()=>{
console.log( Date.now() - startTime );
setTimeout( timeoutFunc() , 3000 ); // timeoutFunc → timeoutFunc()
};
const startTime = Date.now();
setTimeout( timeoutFunc() , 3000 ); // timeoutFunc → timeoutFunc()
timeoutFuncに( )がついているため、その場で関数が実行されます。
実行された関数内でも同様に関数が実行されます。
元のコードで「再帰呼び出しではない」と書いていますが、こちらのコードは再帰呼び出しになっています。
停止させる仕組みがないため、無限ループになります。
更新日:2023/02/06
関連記事
スポンサーリンク
記事の内容について
こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。