JavaScriptでwaitを作ろう!初心者でも分かる待機処理の実装

JavaScriptでwait機能を実装する方法を初心者向けに分かりやすく解説。setTimeout、Promise、async/awaitを使った遅延処理の実装方法と実践的な使用例を学びましょう。

Learning Next 運営
27 分で読めます

みなさん、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は、指定した時間後に処理を実行する関数です。

// 基本的なsetTimeout
function 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機能を活用してみませんか?

関連記事