
setTimeoutは、指定した時間が経過した後に一度だけ関数を実行するJavaScriptの標準的な非同期関数。
勉強がてら、その使い方をざざっとまとめてみました。
※記事内で紹介しているHTML・CSS・JavaScript・PHPなどのコードは、作成時点におけるその道のプロではない作成者の最適解です。動作環境やバージョンによって結果が異なる場合があります。コードの使用により生じたいかなる損害やトラブルについても、当サイトは責任を負いかねます。
setTimeoutとは何か
JavaScriptのsetTimeout関数は、指定した時間(ミリ秒)後に特定の処理を実行するためのタイマー機能。基本的な使い方は以下のとおり。
setTimeout(function() {
// ここに実行したいコード
}, 遅延時間);
主な特徴は
- 非同期処理:コードの実行を一時停止せず、指定した時間後にコールバック関数を実行する。
- 一度だけ実行:指定した遅延時間後に1回だけ実行される。
- タイマーID:関数を呼び出すと、戻り値としてそのタイマーを識別するためのIDが返される。
例えば、3秒後にalert表示させたいなら
setTimeout(function() {
alert("3秒経過しました!");
}, 3000);
これを応用して、クリックしたら2秒後にalert表示させてみる。
さらに応用して、ページが読み込まれてから10秒後にPRをモーダル表示するなんてことも可能。
以下は、便宜的にクリックしてから2秒後にモーダル表示させている。
JavaScriptの非同期関数とは
普通の関数は、何かを計算したりデータを取得したりするのに時間がかかると、その間待つことになる。で、その待つ時間がもったいないということで、待たずに次のことを進めることができるのが非同期関数。
setTimeoutは、時間を待っている間に他のコードを実行できるから非同期関数。指定した時間が経過するまでの間に、他の処理を続けることができる。
例えば、このコード
console.log("処理を開始します");
setTimeout(() => {
console.log("5秒後にこのメッセージが表示されます");
}, 5000);
console.log("処理を続けます");
普通の関数であれば、表示される順番は、
「処理を開始します」→「5秒後にこのメッセージが表示されます」→「処理を続けます」
となる。
最後のconsole.log("処理を続けます")の実行は、「5秒後にこのメッセージが表示されます」の表示後に実行される。
しかし、setTimeoutは待っている間に他のコードを実行できるから、実際の表示される順番は、
「処理を開始します」→「処理を続けます」→「5秒後にこのメッセージが表示されます」
となる。setTimeoutは待っている間に他のコードを実行しているのである。
これが非同期関数で、非同期処理を利用することで、プログラムの効率を高めることができる。
普通の非同期関数は、「async」という言葉を頭につけて作り、「await」という言葉を使うと「この作業が終わるまで待ってね」ということができる。
// 普通の書き方だと、待っている間何もできない
function 普通のケーキ作り() {
材料を混ぜる();
オーブンで焼く(); // ここで30分止まる
デコレーションする();
}
// 非同期関数を使うと、待っている間に他のことができる
async function 非同期ケーキ作り() {
材料を混ぜる();
await オーブンで焼く(); // ここで他の作業ができる
デコレーションする();
}
setTimeoutの引数
関数に引数を渡すには、setTimeoutの第3引数以降を使用する。
function greet(name) {
alert("こんにちは、" + name);
}
setTimeout(greet, 2000, "太郎"); // 2秒後に「こんにちは、太郎」と表示
setTimeout の戻り値
setTimeout は数値の「タイマーID」を返す。これは Promise オブジェクトではなく、単なる識別子。
非同期関数(async function で宣言された関数)は自動的に Promise を返すが、setTimeout は Promise を返さないので直接awaitすることができない。
※Promiseの説明は後述
なので、setTimeout を Promise ベースの処理に統合するには、Promise でラップする必要がある。
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
// 使用例
async function example() {
console.log("開始");
await delay(2000);
console.log("2秒後");
}
example();
setTimeoutのキャンセル方法
返ってきた「タイマーID」を使い、clearTimeoutでタイマーをキャンセルできる。
const timerId = setTimeout(function() {
alert("この処理は実行されません");
}, 2000);
clearTimeout(timerId); // タイマーをキャンセル
タイマーをキャンセルする点については、一見すると矛盾しているように感じられるが「何かが起きたら〜秒後に処理する」というsetTimeout設計において、「何か別のことが起きたらその処理をキャンセルする」という柔軟性を提供する重要な機能である。
ユーザー操作に基づくキャンセル
// 自動保存の例
let saveTimerId;
function startAutoSave() {
// 5秒後に保存処理を実行するタイマーを設定
saveTimerId = setTimeout(function() {
saveDocument();
}, 5000);
}
function cancelAutoSave() {
// ユーザーが「キャンセル」ボタンを押した場合
clearTimeout(saveTimerId);
}
// ユーザーが入力を開始したときに自動保存タイマーを開始
document.getElementById('editor').addEventListener('input', function() {
// 既存のタイマーがあればキャンセル(リセット)
if (saveTimerId) {
clearTimeout(saveTimerId);
}
// 新しいタイマーを開始
startAutoSave();
});
デバウンス(連続した処理の制御)
// 検索入力のデバウンス例
let searchTimerId;
document.getElementById('searchInput').addEventListener('input', function() {
// 既存のタイマーをキャンセル
if (searchTimerId) {
clearTimeout(searchTimerId);
}
// 500ミリ秒の間入力がなければ検索を実行
searchTimerId = setTimeout(function() {
performSearch();
}, 500);
});
条件に基づいたキャンセル
// ログアウト警告の例
let logoutWarningTimer;
function startSession() {
// 5分後にログアウト警告を表示
logoutWarningTimer = setTimeout(function() {
showLogoutWarning();
}, 5 * 60 * 1000);
}
function userActivity() {
// ユーザーが操作を行った場合、既存の警告タイマーをキャンセル
clearTimeout(logoutWarningTimer);
// 新しい警告タイマーを設定
startSession();
}
// ユーザーの操作(クリック、キー入力など)を検知
document.addEventListener('click', userActivity);
document.addEventListener('keypress', userActivity);
エラー処理やタイムアウト制御
// APIリクエストのタイムアウト例
function fetchData() {
let timeoutId;
// タイムアウト処理
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(new Error('リクエストがタイムアウトしました'));
}, 5000);
});
// 実際のデータ取得
const fetchPromise = fetch('https://api.example.com/data')
.then(response => {
// 成功したらタイムアウトをキャンセル
clearTimeout(timeoutId);
return response.json();
});
// どちらか早い方を採用
return Promise.race([fetchPromise, timeoutPromise]);
}
setTimeoutを繰り返し実行したい場合
繰り返し処理にはsetIntervalを使うのが一般的だが、setTimeoutを使って再帰的に呼び出す方法もある。
setTimeoutを使って処理を繰り返すには、関数の中で自分自身を再度呼び出す方法(再帰呼び出し)を使う。
// 再帰的なsetTimeout
function repeatMessage() {
console.log("繰り返し表示");
setTimeout(repeatMessage, 1000);
}
repeatMessage();
この方法では、1回ずつ次のsetTimeoutを呼び出しているため、「前の処理が終わってから次を呼ぶ」という順序となる。つまり、前回の「完了時間」から次の間隔を計測している。
一方のsetIntervalは、「前の処理が終わってから次を呼ぶ」のではなく、「開始時間」から計測しており、指定した間隔(この場合1000ミリ秒=1秒)で繰り返し処理を実行してくれるもの。
// serInterval
setInterval(() => {
console.log("1秒ごとに表示されます");
}, 1000);
setTimeoutとsetInterval
シンプルなのはsetInterval。より細かい制御が必要なときはsetTimeoutの再帰呼び出し。
機能 | setTimeout | setInterval |
---|---|---|
実行回数 | 1回 | 複数回 |
制御しやすさ | 高い(逐次処理可) | 低い(処理時間の影響あり) |
1. setInterval
シンプルな定期実行に向いている。処理時間が短く、一定間隔で実行したい場合に最適。
2. setTimeout
以下の場合に向いている。
・処理時間にばらつきがある
・動的に間隔を変えたい
・前の処理の完了から次の開始までの時間を一定にしたい
エラー処理の違い
1. setInterval
・一回のエラーが発生しても繰り返しは続く
2. setTimeout
・エラーが発生すると再帰呼び出しがなくなるため停止する
・エラー処理をしっかり行う必要がある
// setTimeoutでのエラー処理例
function safeRepeatWithTimeout() {
try {
// 何らかの処理(エラーが発生する可能性あり)
riskyOperation();
console.log("処理成功");
} catch (error) {
console.error("エラーが発生しましたが、続行します:", error);
}
// エラーがあっても次の実行をスケジュール
setTimeout(safeRepeatWithTimeout, 1000);
}
動的な間隔変更
1. setInterval
・一度設定した間隔は変更できない(停止して再設定が必要)
2. setTimeout
・毎回異なる間隔を設定できるため、動的に変更可能
// 動的な間隔でsetTimeoutを使う例
function dynamicRepeat(count = 0) {
console.log(`${count}回目の実行`);
// 実行回数に応じて間隔を変える(徐々に遅くなる)
const nextDelay = 1000 + (count * 500);
console.log(`次は${nextDelay}ミリ秒後に実行します`);
setTimeout(() => dynamicRepeat(count + 1), nextDelay);
}
// 開始
dynamicRepeat();
カウントダウンタイマー実装例
1. setInterval
function countdownWithInterval(seconds) {
let remainingTime = seconds;
// 表示を更新
console.log(`残り時間: ${remainingTime}秒`);
const timerId = setInterval(function() {
// 1秒減らす
remainingTime--;
// 表示を更新
console.log(`残り時間: ${remainingTime}秒`);
// 終了条件
if (remainingTime <= 0) {
clearInterval(timerId); // 繰り返しを停止
console.log("カウントダウン終了!");
}
}, 1000);
}
// 10秒からカウントダウン開始
countdownWithInterval(10);
2. setTimeout
function countdownWithTimeout(seconds) {
let remainingTime = seconds;
// 表示を更新
console.log(`残り時間: ${remainingTime}秒`);
function tick() {
// 1秒減らす
remainingTime--;
// 表示を更新
console.log(`残り時間: ${remainingTime}秒`);
// 終了条件
if (remainingTime > 0) {
// まだ終わっていなければ次の呼び出しをスケジュール
setTimeout(tick, 1000);
} else {
console.log("カウントダウン終了!");
}
}
// 最初のtickを1秒後に実行
setTimeout(tick, 1000);
}
// 10秒からカウントダウン開始
countdownWithTimeout(10);
setTimeoutの遅延時間0
setTimeout(fn, 0)は関数をできるだけ早く、しかし現在の実行スタックの後に実行するためのテクニック。実際には最小遅延(通常4ms)が適用される。
console.log("1");
setTimeout(() => {
console.log("4");
}, 0);
console.log("2");
console.log("3");
// 出力:
// 1
// 2
// 3
// 4
setTimeout(fn, 0) は一見奇妙に見えるが、実際には多くの実用的なシナリオで活用されている。これは「現在の実行スタックの後に、できるだけ早く実行する」という意味を持つ。
イベント発火後の状態リセット
イベント処理後に状態をリセットしたい場合に使用する。
const button = document.getElementById('submit-button');
button.addEventListener('click', function() {
// ボタンを無効化
button.disabled = true;
// フォーム送信など
submitForm();
// 少し経ってからボタンを再度有効化
setTimeout(() => {
button.disabled = false;
}, 0);
});
無限ループの回避
再帰的な処理で、スタックオーバーフローを防ぐために使う。
function recursiveFunction(count) {
console.log(count);
if (count > 0) {
// 直接再帰呼び出しの代わりに setTimeout を使用
setTimeout(() => recursiveFunction(count - 1), 0);
}
}
recursiveFunction(10000); // スタックオーバーフローを起こさない
Promiseって何?
Promiseは「約束」という意味の英語。JavaScriptでは、「今はわからないけど、あとで答えるね」という約束事。
- Promiseには結果が出たあとに「then」で成功した時の処理、「catch」で失敗した時の処理を書く。
- 非同期関数の中では「await」を使うと、Promiseの結果が出るまで待ってくれる。
- 非同期関数は自動的にこのPromiseを返す
// お菓子屋さんにケーキがあるか聞く関数
function ケーキを注文する() {
return new Promise(function(成功したとき, 失敗したとき) {
// お菓子屋さんが考えている...
if (ケーキがある) {
成功したとき("はい、ケーキありますよ!");
} else {
失敗したとき("すみません、ケーキ売り切れです");
}
});
}
// 使い方
ケーキを注文する()
.then(function(メッセージ) {
console.log("やった!" + メッセージ); // 成功したとき
})
.catch(function(メッセージ) {
console.log("残念..." + メッセージ); // 失敗したとき
});