導入:なぜsetIntervalの利用には注意が必要か
Webフロントエンド開発において、タイマー処理は欠かせない機能です。しかし、標準APIである「setInterval」を安易に使い続けることは、予期せぬバグやパフォーマンス低下を招く原因となります。特に、非同期処理を繰り返す場合や、ブラウザの負荷状況によって実行間隔がズレるという特性を理解していないと、UXを損なうことになります。本記事では、setIntervalの仕組みと、実務で推奨されるより堅牢な実装パターンを解説します。
基礎知識:setIntervalの仕組みと課題
setIntervalは、指定したミリ秒間隔で関数を繰り返し実行するためのAPIです。しかし、以下の特性から実務では注意が必要です。
・実行時間の考慮がない:関数内の処理に時間がかかる場合、前回の処理が終わる前に次の実行がキューイングされ、実行間隔が詰まったり、処理が重なったりします。
・ブラウザのバックグラウンド制限:タブが非アクティブな際、ブラウザは省電力のためにタイマーの間隔を意図的に遅らせます。
・エラーによる停止:内部の処理で例外が発生した場合、タイマーが停止したことに気づきにくいといった構造上の弱点があります。
実装/解決策:再帰的setTimeoutによる制御
実務におけるベストプラクティスは、setIntervalを使わず、「再帰的にsetTimeoutを呼び出す」手法です。この方法であれば、前回の処理が終了した後に次のタイマーがセットされるため、処理の重なりを確実に回避できます。
サンプルプログラム:再帰的setTimeoutの実装例
以下のコードは、APIのポーリング処理などを想定した、安全な再帰呼び出しのサンプルです。
// 再帰的に実行するタイマー関数
const pollData = async (interval) => {
try {
// 1. 実際の処理(APIリクエストなどを想定)
console.log("データを取得中...");
// ここで重い処理が発生しても、完了まで次の実行は待機される
await new Promise(resolve => setTimeout(resolve, 500));
console.log("取得完了");
} catch (error) {
console.error("エラー発生:", error);
} finally {
// 2. 処理完了後に次のタイマーをセットする(再帰)
// これにより、処理時間がインターバルに加算されず、一定間隔を維持できる
setTimeout(() => pollData(interval), interval);
}
};
// 実行開始(2秒ごとに実行)
pollData(2000);
応用・注意点:現場で役立つTips
1. メモリリークの回避:React等のフレームワークを使用している場合、コンポーネントのアンマウント時にタイマーをクリアしないとメモリリークが発生します。useEffectのクリーンアップ関数で必ずclearTimeoutを呼び出してください。
2. RequestAnimationFrameとの使い分け:画面の描画更新(アニメーション)が目的であれば、setTimeoutではなく「requestAnimationFrame」を使用するのがフロントエンドの鉄則です。ディスプレイのリフレッシュレートに同期するため、滑らかで効率的な描画が可能です。
3. 正確な時刻管理:ミリ秒単位の厳密な同期が必要な場合は、タイマーの累積誤差を計算して補正するロジックが必要です。単純なsetIntervalでは累積誤差によって時間がずれていくことを忘れないようにしましょう。
実務では「何が起きても安定して動き続けるコード」が求められます。setIntervalの単純な利用から一歩進んで、再帰的な設計を検討してみてください。