JavaScriptでsleep機能を作る方法 - 待機処理の実装パターン

JavaScriptにはsleep関数がないため、setTimeout、Promise、async/awaitを使った待機処理の実装方法を初心者向けに詳しく解説します。

Learning Next 運営
34 分で読めます

みなさん、JavaScriptで作業していて困ったことはありませんか?

「処理を一時停止したい」「他の言語のsleep関数みたいなものが欲しい」「一定時間待ってから次の処理を実行したい」

こんな要求は、プログラミングでとてもよくあることですよね。 でも実は、JavaScriptにはsleep関数が存在しないんです。

この記事では、JavaScriptでsleep機能を実現する様々な方法を初心者向けに分かりやすく解説します。 基本的な仕組みから実践的な活用例まで、一緒に学んでいきましょう!

なぜJavaScriptにsleep関数がないの?

JavaScriptの特殊事情

JavaScriptにsleep関数がない理由は、この言語の動作環境にあります。

簡単に言うと、「ブラウザで動くJavaScriptが完全に止まってしまうと、ページ全体が応答しなくなってしまう」からです。 そのため、処理を止めるのではなく非同期処理で待機を実現する必要があります。

他の言語とJavaScriptの違いを比較してみましょう。

// ❌ JavaScriptには存在しない(他の言語では一般的)
// sleep(1000); // 1秒間プログラム全体を停止
// ✅ JavaScriptでは非同期処理で待機を実現
setTimeout(() => {
console.log("1秒後に実行される");
}, 1000);
console.log("この処理は即座に実行される");

この例では、setTimeoutが1秒の待機を設定しても、その間に他の処理(ここでは最後のconsole.log)が実行されます。 これが非同期処理の特徴です。

非同期処理のメリット

非同期処理には大きなメリットがあります。

  • ブラウザが固まらない: ページが応答し続ける
  • 効率的な処理: 待機中に他の作業ができる
  • ユーザー体験の向上: クリックやタイピングが可能

この仕組みを理解すれば、JavaScriptでも快適な待機処理が実装できます。

方法1: setTimeoutで基本的な遅延実行

最もシンプルな待機方法

setTimeoutは、指定した時間後に処理を実行する最も基本的な方法です。

// 基本的な使い方
function showMessage() {
console.log("3秒後に表示されます");
}
setTimeout(showMessage, 3000); // 3秒後に実行
// 無名関数を使った場合
setTimeout(() => {
console.log("2秒後に表示されます");
}, 2000);

このコードでは、setTimeout関数に実行したい処理と**待機時間(ミリ秒)**を渡しています。 3000ミリ秒は3秒、2000ミリ秒は2秒という意味です。

実際の画面で使ってみよう

HTML要素と組み合わせた実用的な例を見てみましょう。

// ページ読み込み後にメッセージを表示
function showWelcomeMessage() {
// 2秒後にメッセージを表示
setTimeout(() => {
const messageElement = document.getElementById('welcome');
if (messageElement) {
messageElement.textContent = 'ようこそ!サイトへお越しいただきありがとうございます';
messageElement.style.display = 'block';
}
}, 2000);
}
// ボタンクリック後の処理例
function handleSubmitButton() {
const button = document.getElementById('submitBtn');
// ボタンを無効化
button.disabled = true;
button.textContent = '送信中...';
// 3秒後にボタンを復活
setTimeout(() => {
button.disabled = false;
button.textContent = '送信完了!';
// さらに2秒後に元に戻す
setTimeout(() => {
button.textContent = '送信';
}, 2000);
}, 3000);
}

この例では、まずボタンを無効化してから3秒後に状態を変更し、さらに2秒後に元に戻しています。 ユーザーに処理の進行状況を分かりやすく伝えることができますね。

連続した処理を実装しよう

複数のメッセージを順番に表示する機能を作ってみましょう。

function showSequentialMessages() {
const messages = [
"処理を開始します...",
"データを読み込んでいます...",
"処理中です...",
"もう少しお待ちください...",
"完了しました!"
];
// 各メッセージを1.5秒ずつ遅延して表示
messages.forEach((message, index) => {
setTimeout(() => {
console.log(message);
// 画面にも表示
const statusElement = document.getElementById('status');
if (statusElement) {
statusElement.textContent = message;
}
}, index * 1500); // 0秒、1.5秒、3秒、4.5秒、6秒
});
}

forEach文でindex(順番番号)を取得し、それに1500をかけることで等間隔の遅延を実現しています。 このように、setTimeout を組み合わせることで複雑な処理順序も制御できます。

方法2: Promiseでモダンな待機処理

sleep関数を自作しよう

Promiseを使うことで、より使いやすいsleep関数を作ることができます。

// sleep関数の作成
function sleep(milliseconds) {
return new Promise(resolve => {
setTimeout(resolve, milliseconds);
});
}
// 使用例
sleep(2000).then(() => {
console.log("2秒後に実行されました");
});

この関数は、指定したミリ秒数だけ待機するPromiseを返します。 setTimeoutよりも直感的で、他の非同期処理と組み合わせやすくなります。

チェーン処理で段階的な実行

Promiseの特徴であるチェーン処理を活用してみましょう。

function demonstrateChainedDelay() {
console.log("処理開始");
sleep(1000)
.then(() => {
console.log("1秒経過 - 第1段階完了");
return sleep(1500);
})
.then(() => {
console.log("2.5秒経過 - 第2段階完了");
return sleep(800);
})
.then(() => {
console.log("3.3秒経過 - 第3段階完了");
return sleep(1200);
})
.then(() => {
console.log("4.5秒経過 - 全ての処理が完了");
})
.catch(error => {
console.error("エラーが発生しました:", error);
});
}

.then()を使って処理を順番につなげることで、複雑な待機パターンを見やすく実装できます。 各段階で異なる待機時間を設定することも簡単です。

エラーハンドリングも組み込もう

実際のアプリケーションでは、エラー処理も重要です。

function safeDelayedProcess() {
const startTime = Date.now();
console.log("安全な遅延処理を開始します");
sleep(2000)
.then(() => {
const elapsed = Date.now() - startTime;
console.log(`実際の経過時間: ${elapsed}ms`);
// 何らかの条件でエラーを発生させる例
if (Math.random() > 0.7) {
throw new Error("ランダムエラーが発生しました");
}
return "処理が正常に完了しました";
})
.then(result => {
console.log("成功:", result);
})
.catch(error => {
console.error("エラーを捕捉しました:", error.message);
console.log("エラーが発生しましたが、処理を継続します");
});
}

try-catch のような感覚で .catch() を使ってエラー処理ができます。 これにより、待機処理中に問題が発生しても適切に対応できます。

方法3: async/awaitで最も読みやすく

同期処理のような書き方

async/awaitを使うと、非同期処理を同期処理のように書けて非常に読みやすくなります。

// sleep関数(再利用)
function sleep(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds));
}
// async関数での使用
async function delayedProcess() {
console.log("処理開始");
await sleep(1000);
console.log("1秒後");
await sleep(2000);
console.log("さらに2秒後");
await sleep(1000);
console.log("処理完了");
}
// 実行
delayedProcess();

await キーワードを使うことで、Promiseの結果を「待つ」ことができます。 コードが上から下に順番に実行されるように見えるため、理解しやすいのが特徴です。

実用的なデータ取得の例

API呼び出しの模擬処理を実装してみましょう。

async function fetchUserData() {
try {
console.log("ユーザーデータを取得中...");
// ローディング表示
showLoading(true);
// API呼び出しの模擬(3秒の遅延)
await sleep(3000);
// データ取得の模擬
const userData = {
name: "田中太郎",
age: 28,
department: "開発部",
lastLogin: new Date().toISOString()
};
console.log("データ取得完了:", userData);
// 結果を画面に表示
displayUserData(userData);
return userData;
} catch (error) {
console.error("データ取得エラー:", error);
showError("ユーザーデータの取得に失敗しました");
} finally {
// 必ず実行される処理
showLoading(false);
}
}
function showLoading(isVisible) {
const loader = document.getElementById('loader');
if (loader) {
loader.style.display = isVisible ? 'block' : 'none';
}
}
function displayUserData(data) {
const userInfo = document.getElementById('userInfo');
if (userInfo) {
userInfo.innerHTML = `
<h3>ユーザー情報</h3>
<p>名前: ${data.name}</p>
<p>年齢: ${data.age}</p>
<p>部署: ${data.department}</p>
<p>最終ログイン: ${data.lastLogin}</p>
`;
}
}
function showError(message) {
const errorDiv = document.getElementById('error');
if (errorDiv) {
errorDiv.textContent = message;
errorDiv.style.display = 'block';
}
}

この例では、try-catch-finally構文を使って完全なエラーハンドリングを実装しています。 finally ブロックは成功・失敗に関わらず必ず実行されるため、ローディング表示の非表示化などに最適です。

複数の処理を組み合わせよう

複雑な処理フローも async/await なら分かりやすく書けます。

async function complexProcessFlow() {
const steps = [
{ name: "初期化", duration: 800 },
{ name: "設定読み込み", duration: 1200 },
{ name: "データベース接続", duration: 2000 },
{ name: "ユーザー認証", duration: 1500 },
{ name: "メニュー構築", duration: 900 }
];
console.log("複合処理を開始します");
const startTime = Date.now();
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
console.log(`ステップ${i + 1}: ${step.name}を実行中...`);
// 進捗表示
updateProgress(i + 1, steps.length);
// 待機
await sleep(step.duration);
console.log(`ステップ${i + 1}: ${step.name}が完了`);
}
const totalTime = Date.now() - startTime;
console.log(`全処理完了 (総時間: ${totalTime}ms)`);
}
function updateProgress(current, total) {
const percentage = Math.round((current / total) * 100);
const progressBar = document.getElementById('progressBar');
if (progressBar) {
progressBar.style.width = percentage + '%';
progressBar.textContent = `${current}/${total} (${percentage}%)`;
}
}

for ループと await を組み合わせることで、各ステップを順次実行できます。 進捗表示も含めた実用的な処理フローが簡潔に書けますね。

方法4: setIntervalで繰り返し処理

定期的に実行する処理

setIntervalは、指定した間隔で処理を繰り返し実行します。

// 基本的なsetInterval
const intervalId = setInterval(() => {
console.log("1秒ごとに実行される処理");
console.log("現在時刻:", new Date().toLocaleTimeString());
}, 1000);
// 10秒後に停止
setTimeout(() => {
clearInterval(intervalId);
console.log("定期実行を停止しました");
}, 10000);

setInterval は「間隔ID」を返すので、それを clearInterval に渡すことで停止できます。 無限に実行され続けないよう、適切なタイミングで停止することが重要です。

カウントダウンタイマーを作ろう

実用的なカウントダウン機能を実装してみましょう。

function createCountdownTimer(seconds) {
let remaining = seconds;
// 即座に初期値を表示
displayCountdown(remaining);
const countdownInterval = setInterval(() => {
remaining--;
console.log(`残り${remaining}`);
displayCountdown(remaining);
if (remaining <= 0) {
clearInterval(countdownInterval);
console.log("カウントダウン完了!");
onCountdownComplete();
}
}, 1000);
return countdownInterval; // 停止用のIDを返す
}
function displayCountdown(seconds) {
const timerElement = document.getElementById('timer');
if (timerElement) {
if (seconds > 0) {
timerElement.textContent = `残り${seconds}`;
timerElement.style.color = seconds <= 5 ? 'red' : 'black';
} else {
timerElement.textContent = "時間終了!";
timerElement.style.color = 'red';
timerElement.style.fontWeight = 'bold';
}
}
}
function onCountdownComplete() {
alert("時間になりました!");
// 何らかの処理を実行
const resultElement = document.getElementById('result');
if (resultElement) {
resultElement.textContent = "タイマーが完了しました";
}
}
// 使用例
const timer = createCountdownTimer(30); // 30秒のカウントダウン
// 途中で停止したい場合
// clearInterval(timer);

残り時間が5秒以下になると文字色を赤に変更するなど、ユーザー体験を向上させる工夫も含まれています。

アニメーション効果も作れます

プログレスバーのアニメーションを実装してみましょう。

function animateProgressBar(duration = 5000) {
const progressBar = document.getElementById('progressBar');
if (!progressBar) {
console.error("プログレスバー要素が見つかりません");
return;
}
let progress = 0;
const updateInterval = 50; // 50msごとに更新(滑らかなアニメーション)
const increment = (100 / duration) * updateInterval;
const progressInterval = setInterval(() => {
progress += increment;
if (progress >= 100) {
progress = 100;
clearInterval(progressInterval);
console.log("プログレスバー完了");
onProgressComplete();
}
// プログレスバーの幅と表示テキストを更新
progressBar.style.width = progress + '%';
const percentText = Math.round(progress);
progressBar.textContent = percentText + '%';
// 色の変化も追加
if (progress < 30) {
progressBar.style.backgroundColor = '#ff6b6b'; // 赤
} else if (progress < 70) {
progressBar.style.backgroundColor = '#ffd93d'; // 黄
} else {
progressBar.style.backgroundColor = '#6bcf7f'; // 緑
}
}, updateInterval);
}
function onProgressComplete() {
console.log("処理が完了しました!");
setTimeout(() => {
alert("すべての処理が完了しました");
}, 500);
}

50ミリ秒という短い間隔で更新することで、滑らかなアニメーション効果を実現しています。 進捗に応じて色を変化させる演出も追加しています。

高度なテクニック: 条件待ちとリトライ処理

特定の条件が満たされるまで待機

実際のアプリケーションでは、「特定の要素が表示されるまで待つ」といった複雑な待機が必要になることがあります。

async function waitForCondition(checkFunction, options = {}) {
const {
checkInterval = 100, // チェック間隔(ミリ秒)
timeout = 5000, // タイムアウト時間(ミリ秒)
message = "条件待ち" // ログメッセージ
} = options;
const startTime = Date.now();
while (true) {
// 条件をチェック
if (checkFunction()) {
console.log(`${message}: 条件が満たされました`);
return true;
}
// タイムアウトチェック
const elapsed = Date.now() - startTime;
if (elapsed > timeout) {
throw new Error(`${message}: ${timeout}ms以内に条件が満たされませんでした`);
}
// 次のチェックまで待機
await sleep(checkInterval);
}
}
// 使用例: 要素の表示を待つ
async function waitForElementExample() {
try {
console.log("要素の表示を待機中...");
await waitForCondition(() => {
const element = document.getElementById('dynamicContent');
return element && element.style.display !== 'none';
}, {
checkInterval: 200, // 200msごとにチェック
timeout: 10000, // 10秒でタイムアウト
message: "要素表示待ち"
});
console.log("要素が表示されました!");
} catch (error) {
console.error("待機エラー:", error.message);
}
}

この関数を使うことで、DOM要素の表示待ちやAPIレスポンス待ちなど、様々な条件待ちを実装できます。

失敗時のリトライ機能

エラーが発生した場合に自動的に再試行する機能も作ってみましょう。

async function retryWithDelay(asyncFunction, options = {}) {
const {
maxRetries = 3, // 最大試行回数
retryDelay = 1000, // 再試行間隔(ミリ秒)
backoffMultiplier = 2 // 遅延時間の倍率
} = options;
let currentDelay = retryDelay;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`試行 ${attempt}/${maxRetries}`);
const result = await asyncFunction();
console.log(`試行 ${attempt} で成功しました`);
return result;
} catch (error) {
console.error(`試行 ${attempt} 失敗:`, error.message);
if (attempt === maxRetries) {
throw new Error(`${maxRetries}回の試行後も失敗: ${error.message}`);
}
console.log(`${currentDelay}ms後に再試行します...`);
await sleep(currentDelay);
// 次回の遅延時間を増加(指数バックオフ)
currentDelay *= backoffMultiplier;
}
}
}
// 使用例: 不安定なAPI呼び出し
async function unreliableApiCall() {
// 70%の確率で失敗する模擬API
await sleep(500); // API呼び出しの遅延をシミュレート
if (Math.random() > 0.3) {
throw new Error("ネットワークエラーが発生しました");
}
return { data: "API呼び出し成功", timestamp: new Date().toISOString() };
}
// リトライ機能を使用
async function demonstrateRetry() {
try {
const result = await retryWithDelay(unreliableApiCall, {
maxRetries: 5,
retryDelay: 500,
backoffMultiplier: 1.5
});
console.log("最終的に成功:", result);
} catch (error) {
console.error("全ての試行が失敗しました:", error.message);
}
}

指数バックオフ(徐々に待機時間を長くする)を実装することで、サーバーへの負荷を軽減しながら再試行できます。

実践的な活用例

フォーム送信での待機演出

実際のWebアプリケーションでよく使われるパターンを実装してみましょう。

async function handleFormSubmission() {
const submitButton = document.getElementById('submitBtn');
const statusMessage = document.getElementById('statusMessage');
const form = document.getElementById('userForm');
try {
// ボタンを無効化して送信中状態に
submitButton.disabled = true;
submitButton.textContent = '送信中...';
statusMessage.textContent = 'データを送信しています...';
// 送信処理のシミュレーション
await sleep(1000);
statusMessage.textContent = 'サーバーで処理中...';
await sleep(2000);
statusMessage.textContent = 'データを保存中...';
await sleep(1000);
// 成功時の処理
statusMessage.textContent = '送信完了!';
statusMessage.style.color = 'green';
submitButton.textContent = '送信完了';
// 2秒後にフォームをリセット
await sleep(2000);
form.reset();
submitButton.disabled = false;
submitButton.textContent = '送信';
statusMessage.textContent = '';
statusMessage.style.color = 'black';
} catch (error) {
console.error('送信エラー:', error);
statusMessage.textContent = '送信に失敗しました';
statusMessage.style.color = 'red';
submitButton.disabled = false;
submitButton.textContent = '再送信';
}
}

このパターンを使うことで、ユーザーに処理の進行状況を分かりやすく伝えることができます。

段階的なページローディング

ページの読み込みを演出する機能も実装してみましょう。

async function stagePageLoading() {
const stages = [
{ message: "ページを準備しています...", duration: 800 },
{ message: "スタイルを読み込んでいます...", duration: 600 },
{ message: "データを取得しています...", duration: 1200 },
{ message: "コンテンツを構築しています...", duration: 900 },
{ message: "最終調整中...", duration: 400 }
];
const loadingDiv = document.getElementById('loading');
const contentDiv = document.getElementById('content');
// ローディング画面を表示
loadingDiv.style.display = 'block';
contentDiv.style.display = 'none';
for (let i = 0; i < stages.length; i++) {
const stage = stages[i];
// メッセージを更新
const messageElement = document.getElementById('loadingMessage');
if (messageElement) {
messageElement.textContent = stage.message;
}
// プログレスバーを更新
const progressBar = document.getElementById('loadingProgress');
if (progressBar) {
const percentage = Math.round(((i + 1) / stages.length) * 100);
progressBar.style.width = percentage + '%';
}
console.log(`ステージ${i + 1}: ${stage.message}`);
// 待機
await sleep(stage.duration);
}
// ローディング完了
loadingDiv.style.display = 'none';
contentDiv.style.display = 'block';
console.log("ページローディング完了");
}

各段階で異なる待機時間を設定することで、リアルな読み込み体験を演出できます。

まとめ

JavaScriptでのsleep機能は、適切な方法を選択することで快適に実装できます

重要なポイントをおさらいしましょう。

  • setTimeout: 最も基本的で理解しやすい
  • Promise + sleep関数: チェーン処理で柔軟な制御
  • async/await: 最も読みやすく直感的な記述
  • setInterval: 繰り返し処理や定期実行に最適

用途に応じた使い分けが大切です。

  • シンプルな遅延: setTimeout
  • 複雑な処理フロー: async/await + sleep関数
  • 定期的な更新: setInterval
  • 条件待ちやリトライ: 高度なパターンを組み合わせ

実践で役立つテクニックも身につきました。

  • エラーハンドリングを含む安全な実装
  • ユーザー体験を向上させる視覚的フィードバック
  • 条件待ちやリトライ機能で堅牢性を向上

これらの知識を活用することで、ユーザーにとって快適で分かりやすい待機処理を実装できます。 ぜひ実際のプロジェクトで試してみて、JavaScriptの非同期処理をマスターしてくださいね!

関連記事