JavaScriptでwaitを作ろう!初心者でも分かる待機処理の実装
JavaScriptでwait機能を実装する方法を初心者向けに分かりやすく解説。setTimeout、Promise、async/awaitを使った遅延処理の実装方法と実践的な使用例を学びましょう。
みなさん、JavaScriptを書いていてこんな経験はありませんか?
「処理を少し待たせたい」「他の言語のsleep関数みたいなのはないの?」「アニメーションの間隔を調整したい」
こんな悩み、実はとても多いんです。
JavaScriptには他の言語のようなsleep
関数はありませんが、同じようなwait機能を作ることができるんです。
この記事では、JavaScriptでwait機能を実装する方法を初心者向けに分かりやすく解説します。 setTimeout、Promise、async/awaitを使った遅延処理の実装方法を、実例付きで一緒に学んでいきましょう!
なぜJavaScriptにwait関数がないの?
JavaScriptの特殊な仕組み
JavaScriptはシングルスレッドで動作します。 簡単に言うと、一度に一つの処理しかできない仕組みなんです。
// もしこんな関数があったら(実際には存在しません)function syncExample() { console.log("処理開始"); // sleep(3000); // 3秒間完全に停止 console.log("処理終了");}
もしこんな風に処理が完全に止まってしまったら、その間ユーザーは何もできなくなってしまいます。 ボタンをクリックしても反応しない、スクロールもできない、まるでフリーズしたような状態になるんです。
非同期処理という解決策
そこでJavaScriptは非同期処理という仕組みを使います。
// JavaScriptの推奨方法function asyncExample() { console.log("処理開始"); setTimeout(() => { console.log("3秒後の処理"); }, 3000); console.log("処理終了(すぐに実行される)");}
asyncExample();
この例を実行すると、以下の順番で表示されます。
処理開始
処理終了(すぐに実行される)
3秒後の処理 (3秒後)
処理が止まることなく、他の操作も続けられるのが分かりますね。
setTimeoutを使った基本的なwait
最もシンプルな遅延処理
setTimeoutは、指定した時間後に処理を実行する関数です。
// 基本的なsetTimeoutfunction basicTimeout() { console.log("開始"); setTimeout(function() { console.log("3秒後に実行"); }, 3000); console.log("終了(すぐに実行)");}
basicTimeout();
setTimeout
は次のように使います。
- 第1引数:実行したい処理(関数)
- 第2引数:待機時間(ミリ秒)
アロー関数でもっとスッキリ
アロー関数を使うと、より短く書けます。
// アロー関数での書き方function arrowTimeout() { console.log("開始"); setTimeout(() => { console.log("2秒後に実行"); }, 2000); console.log("終了(すぐに実行)");}
arrowTimeout();
アロー関数(() => {}
)を使うことで、よりシンプルに書けますね。
複数のタイマーを同時に設定
一度に複数のタイマーを設定することもできます。
function multipleTimeouts() { console.log("開始"); setTimeout(() => console.log("1秒後"), 1000); setTimeout(() => console.log("2秒後"), 2000); setTimeout(() => console.log("3秒後"), 3000); console.log("終了(すぐに実行)");}
multipleTimeouts();
実行すると、時間差で順番にメッセージが表示されます。
setTimeoutの問題点
ただし、setTimeoutには問題もあります。
// 問題:コールバック地獄function callbackHell() { console.log("開始"); setTimeout(() => { console.log("1秒後"); setTimeout(() => { console.log("2秒後"); setTimeout(() => { console.log("3秒後"); setTimeout(() => { console.log("4秒後"); // さらにネストが深くなる... }, 1000); }, 1000); }, 1000); }, 1000);}
このように、処理が深くネストしてしまう「コールバック地獄」という問題があります。
Promiseを使ったwait関数
基本的なwait関数を作ろう
Promiseを使うことで、より使いやすいwait関数が作れます。
// Promiseを使ったwait関数function wait(milliseconds) { return new Promise(resolve => { setTimeout(resolve, milliseconds); });}
このwait
関数は、指定した時間だけ待機するPromiseを返します。
wait関数の使い方
作ったwait関数を使ってみましょう。
// 使用例function useWaitFunction() { console.log("開始"); wait(2000).then(() => { console.log("2秒後に実行"); return wait(1000); }).then(() => { console.log("さらに1秒後に実行"); return wait(1500); }).then(() => { console.log("さらに1.5秒後に実行"); }); console.log("終了(すぐに実行)");}
useWaitFunction();
.then()
でつなげることで、順番に処理を実行できます。
コールバック地獄よりもずっと読みやすくなりましたね。
より高度なwait関数
値を返したり、エラーも扱えるwait関数も作れます。
// 値を返すwait関数function waitWithValue(milliseconds, value) { return new Promise(resolve => { setTimeout(() => resolve(value), milliseconds); });}
// エラーも扱えるwait関数function waitWithError(milliseconds, shouldError = false) { return new Promise((resolve, reject) => { setTimeout(() => { if (shouldError) { reject(new Error("タイムアウトエラー")); } else { resolve("成功"); } }, milliseconds); });}
これらの関数を使うことで、より柔軟な遅延処理が可能になります。
async/awaitで更に簡単に
同期処理のように書ける
async/awaitを使うと、まるで同期処理のように書けます。
// async/awaitを使った読みやすいwait処理async function asyncWaitExample() { console.log("開始"); await wait(1000); console.log("1秒後"); await wait(2000); console.log("さらに2秒後"); await wait(1500); console.log("さらに1.5秒後"); console.log("すべて完了");}
asyncWaitExample();
await
を使うことで、処理が順番に実行されるのを確認できます。
とても読みやすくて分かりやすいですね。
エラーハンドリングも簡単
try/catchを使ってエラー処理も簡単に書けます。
async function asyncWithErrorHandling() { try { console.log("処理開始"); await wait(1000); console.log("1秒経過"); let result = await waitWithValue(2000, "重要なデータ"); console.log("取得したデータ:", result); await waitWithError(1000, false); // エラーなし console.log("すべて成功"); } catch (error) { console.log("エラーが発生:", error.message); }}
asyncWithErrorHandling();
エラーが発生しても、catch
ブロックで適切に処理できます。
複数の処理を並行実行
同時に複数の処理を実行することもできます。
// 順次実行(遅い)async function sequentialExecution() { console.time("順次実行"); await wait(1000); console.log("1つ目完了"); await wait(1000); console.log("2つ目完了"); await wait(1000); console.log("3つ目完了"); console.timeEnd("順次実行"); // 約3秒}
// 並行実行(速い)async function parallelExecution() { console.time("並行実行"); let promises = [ wait(1000).then(() => console.log("1つ目完了")), wait(1000).then(() => console.log("2つ目完了")), wait(1000).then(() => console.log("3つ目完了")) ]; await Promise.all(promises); console.timeEnd("並行実行"); // 約1秒}
Promise.all()
を使うことで、複数の処理を同時に実行できます。
実践的な使い方を学ぼう
APIの段階的呼び出し
実際のWebアプリケーションでよくある、段階的なAPI呼び出しの例です。
// 模擬的なAPI関数async function fetchUserData(userId) { await wait(1000); // ネットワーク遅延をシミュレート return { id: userId, name: `ユーザー${userId}`, email: `user${userId}@example.com` };}
async function fetchUserPosts(userId) { await wait(1500); return [ { id: 1, title: "投稿1", content: "内容1" }, { id: 2, title: "投稿2", content: "内容2" } ];}
async function fetchUserComments(userId) { await wait(800); return [ { postId: 1, comment: "コメント1" }, { postId: 2, comment: "コメント2" } ];}
これらのAPI関数を組み合わせて、段階的にデータを取得してみましょう。
// 段階的なデータ取得async function loadUserProfile(userId) { try { console.log("ユーザープロフィール読み込み開始"); // ユーザー基本情報を取得 console.log("基本情報を取得中..."); let user = await fetchUserData(userId); console.log("ユーザー情報:", user); // 短い待機時間を入れてUXを改善 await wait(500); // 投稿とコメントを並行取得 console.log("投稿とコメントを取得中..."); let [posts, comments] = await Promise.all([ fetchUserPosts(userId), fetchUserComments(userId) ]); console.log("投稿:", posts); console.log("コメント:", comments); return { user, posts, comments }; } catch (error) { console.log("データ取得エラー:", error.message); throw error; }}
loadUserProfile(1);
実際のアプリケーションでは、このようにデータを段階的に取得することがよくあります。
アニメーション制御
Webページのアニメーションでもwait機能は活用できます。
// アニメーション用のヘルパー関数function animateElement(element, property, targetValue, duration) { return new Promise(resolve => { element.style.transition = `${property} ${duration}ms ease`; element.style[property] = targetValue; setTimeout(resolve, duration); });}
// 段階的なアニメーションasync function sequentialAnimation() { let element = document.getElementById("animated-box"); if (!element) { console.log("要素が見つかりません"); return; } try { console.log("アニメーション開始"); // 右に移動 await animateElement(element, "transform", "translateX(100px)", 1000); console.log("右移動完了"); // 少し待機 await wait(500); // 下に移動 await animateElement(element, "transform", "translateX(100px) translateY(100px)", 1000); console.log("下移動完了"); // 待機 await wait(500); // 元の位置に戻る await animateElement(element, "transform", "translateX(0) translateY(0)", 1000); console.log("アニメーション完了"); } catch (error) { console.log("アニメーションエラー:", error.message); }}
このように、アニメーションの間隔を調整して自然な動きを作ることができます。
リトライ機能付きの処理
失敗する可能性がある処理に、リトライ機能を付けることもできます。
// リトライ機能付きの関数async function retryWithWait(asyncFunction, maxRetries = 3, waitTime = 1000) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { console.log(`試行 ${attempt}/${maxRetries}`); return await asyncFunction(); } catch (error) { console.log(`試行 ${attempt} 失敗:`, error.message); if (attempt === maxRetries) { throw new Error(`${maxRetries}回の試行後も失敗: ${error.message}`); } // 指数バックオフ(待機時間を徐々に長くする) let backoffTime = waitTime * Math.pow(2, attempt - 1); console.log(`${backoffTime}ms 待機中...`); await wait(backoffTime); } }}
// 失敗する可能性のある処理async function unreliableOperation() { if (Math.random() < 0.7) { // 70%の確率で失敗 throw new Error("ランダムエラー"); } return "成功!";}
// 使用例async function useRetryFunction() { try { let result = await retryWithWait(unreliableOperation, 5, 500); console.log("最終結果:", result); } catch (error) { console.log("最終的に失敗:", error.message); }}
useRetryFunction();
失敗しても諦めずに、時間を置いて再試行してくれます。
高度なテクニック
キャンセル可能なwait
処理を途中でキャンセルできるwait関数も作れます。
// キャンセル可能なwait関数function cancellableWait(milliseconds) { let timeoutId; let promise = new Promise((resolve, reject) => { timeoutId = setTimeout(resolve, milliseconds); }); promise.cancel = () => { clearTimeout(timeoutId); return Promise.reject(new Error("キャンセルされました")); }; return promise;}
// 使用例async function cancellableWaitExample() { let waitPromise = cancellableWait(5000); // 2秒後にキャンセル setTimeout(() => { console.log("待機をキャンセルします"); waitPromise.cancel(); }, 2000); try { await waitPromise; console.log("5秒待機完了"); } catch (error) { console.log("エラー:", error.message); }}
cancellableWaitExample();
長い処理でも、必要に応じてキャンセルできるので便利です。
条件付きwait
特定の条件が満たされるまで待機する関数も作れます。
// 条件が満たされるまで待機する関数async function waitUntil(conditionFunction, checkInterval = 100, timeout = 10000) { let startTime = Date.now(); while (true) { // 条件をチェック if (conditionFunction()) { return true; } // タイムアウトチェック if (Date.now() - startTime > timeout) { throw new Error("タイムアウト: 条件が満たされませんでした"); } // 次のチェックまで待機 await wait(checkInterval); }}
// 使用例async function waitUntilExample() { let counter = 0; // カウンターを徐々に増加 let interval = setInterval(() => { counter++; console.log("カウンター:", counter); }, 500); try { console.log("カウンターが5になるまで待機..."); await waitUntil(() => counter >= 5, 100, 5000); console.log("条件が満たされました!"); } catch (error) { console.log("エラー:", error.message); } finally { clearInterval(interval); }}
waitUntilExample();
データの読み込みが完了するまで待機したい場合などに便利です。
ローディング表示との組み合わせ
ユーザーフレンドリーな待機処理
実際のアプリケーションでは、ローディング表示と組み合わせることが多いです。
// ローディング表示のヘルパークラスclass LoadingManager { constructor() { this.isLoading = false; this.loadingElement = null; } showLoading(message = "読み込み中...") { this.isLoading = true; if (!this.loadingElement) { this.loadingElement = document.createElement("div"); this.loadingElement.id = "loading"; this.loadingElement.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.8); color: white; padding: 20px; border-radius: 5px; z-index: 9999; `; document.body.appendChild(this.loadingElement); } this.loadingElement.textContent = message; this.loadingElement.style.display = "block"; } hideLoading() { this.isLoading = false; if (this.loadingElement) { this.loadingElement.style.display = "none"; } } async withLoading(asyncFunction, message = "処理中...") { try { this.showLoading(message); return await asyncFunction(); } finally { this.hideLoading(); } }}
このLoadingManager
クラスを使うことで、処理中であることをユーザーに分かりやすく伝えられます。
// 使用例async function dataProcessingWithLoading() { let loadingManager = new LoadingManager(); try { // データ取得処理 let data = await loadingManager.withLoading(async () => { await wait(2000); // データ取得をシミュレート return { users: ["太郎", "花子", "次郎"] }; }, "ユーザーデータを取得中..."); console.log("取得したデータ:", data); // データ処理 let processedData = await loadingManager.withLoading(async () => { await wait(1500); // データ処理をシミュレート return data.users.map(name => ({ name, id: Math.random() })); }, "データを処理中..."); console.log("処理済みデータ:", processedData); } catch (error) { console.log("エラー:", error.message); }}
dataProcessingWithLoading();
ユーザーに今何が起こっているかを明確に伝えることで、より良いユーザーエクスペリエンスを提供できます。
まとめ
JavaScriptでのwait機能の実装について学んできました。
基本的な実装方法をおさらいしましょう。
- setTimeout: 基本的な遅延処理
- Promise: より柔軟な非同期制御
- async/await: 読みやすい同期的なコード
実践的なテクニックも身につきました。
- 複数処理の並行実行で効率化
- エラーハンドリングで安全性向上
- リトライ機能で信頼性向上
- キャンセル機能でユーザビリティ向上
高度な機能も覚えておきましょう。
- 条件付き待機で動的な制御
- ローディング表示でUX改善
- メモリ効率を考慮した実装
ベストプラクティスとして以下を意識しましょう。
- ブロッキング処理を避ける
- 適切なエラーハンドリングを行う
- ユーザーエクスペリエンスを考慮する
- メモリリークを防止する
JavaScriptの非同期処理を理解して適切なwait機能を実装することで、より良いユーザーエクスペリエンスを提供できるアプリケーションが作れるようになります。
最初は基本的なPromiseベースのwait関数から始めて、徐々に高度なテクニックも取り入れてみてくださいね。
ぜひ今度のプロジェクトで、これらのwait機能を活用してみませんか?