読み込み中...

JavaScriptで使えるsleep関数の代替実装方法と注意点まとめ

JavaScriptにおけるsleep関数の実装方法まとめ JS
この記事は約19分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●JavaScriptにはsleep関数がない!?

JavaScriptを使っていて、「あれ?ちょっと待ちたいけど、sleep関数ってないの?」と思ったことはありませんか?

実は、JavaScriptには他の言語のようにsleep関数が標準で用意されていないんです。

でも大丈夫、sleep関数と同等の処理は実装できますからね。

○sleep関数が必要な理由

そもそもsleep関数って何のために使うのでしょうか。sleep関数は、指定した時間だけプログラムの実行を一時停止するために使います。

例えば、あるボタンをクリックしたら、少し時間をおいてから次の処理を実行したいときなどに使えます。

これによって、ユーザーに「処理中」という感覚を与えることができるんです。

○標準のsleep関数がない理由

他の言語ではよくあるsleep関数ですが、なぜJavaScriptには用意されていないのでしょうか。

それは、JavaScriptが非同期処理を得意としている言語だからです。

非同期処理では、ある処理を実行している間に、別の処理を並行して進めることができます。

もしsleep関数で処理を止めてしまうと、せっかくの非同期処理のメリットが活かせません。

でも、時には同期的に処理を止めたいシーンもありますよね。

そんなときのために、JavaScriptでもsleep関数と同等の処理を実装する方法があります。

これからそのやり方を詳しく見ていきましょう。

●setTimeoutを使ったsleep関数の実装

JavaScriptでsleep関数と同等の処理を実装する方法の1つが、setTimeoutを使う方法です。

setTimeoutは指定した時間が経過した後に、指定したコールバック関数を実行するメソッドなんですよ。

○サンプルコード1:setTimeoutの基本的な使い方

まずは、setTimeoutの基本的な使い方から見ていきましょう。

こんな感じで書けます。

console.log("処理開始");

setTimeout(function() {
  console.log("3秒後に実行されます");
}, 3000);

console.log("処理終了");

実行結果はこうなります。

処理開始
処理終了
3秒後に実行されます

setTimeoutに渡した関数は、指定した時間(ミリ秒)が経過した後に実行されるんですね。

でも、その間もプログラムは止まらずに先に進んでいます。

これがsetTimeoutの非同期的な性質です。

○サンプルコード2:setTimeoutを使ったsleep関数

setTimeoutを使ってsleep関数っぽいものを作るには、ちょっとややこしいですが、こんな感じに書けます。

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function main() {
  console.log("処理開始");

  await sleep(3000);
  console.log("3秒後に実行されます");

  console.log("処理終了");  
}

main();

実行結果は次のようになります。

処理開始
3秒後に実行されます
処理終了

sleepという関数を定義して、その中でsetTimeoutを使っています。

setTimeoutに渡したコールバック関数が呼ばれるまで、メインの処理を待つようにするためにPromiseを使っているんですね。

メイン処理であるmain関数はasync関数にして、sleep関数をawaitすることで、同期的な処理のように見せています。

○setTimeoutのデメリットと注意点

setTimeoutを使ってsleep関数を実装することはできますが、いくつか注意点があります。

1つは、コードの見通しが悪くなることです。

setTimeoutのコールバックの中に処理を書くと、ネストが深くなってしまいます。

もう1つは、処理を待っている間、完全に処理がブロックされるわけではないということです。

UIスレッドを止めてしまうと、ブラウザがフリーズしたように見えてしまうかもしれません。

そのため、setTimeoutをそのまま使うのではなく、もう少し抽象度の高い方法を使うのがおすすめです。

次はPromiseを使ったsleep関数の実装を見ていきましょう。

●Promiseを使ったsleep関数の実装

JavaScriptでsleep関数を実装するもう1つの方法が、Promiseを使う方法です。

Promiseは非同期処理を扱うための機能で、処理の完了を待ったり、処理結果を受け取ったりできるんですよ。

○サンプルコード3:Promiseの基本的な使い方

Promiseの基本的な使い方から見ていきましょう。

こんな感じで書けます。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promiseが完了しました");
  }, 3000);
});

promise.then((result) => {
  console.log(result);
});

console.log("Promiseの完了を待っています...");

実行結果はこうなります。

Promiseの完了を待っています...
Promiseが完了しました

Promiseのコンストラクタに渡した関数の中で、非同期処理(ここではsetTimeout)を行っています。

処理が完了したらresolveを呼び出して、Promiseを完了状態にします。

そして、promise.thenで完了後の処理を記述しているんですね。

○サンプルコード4:Promiseを使ったsleep関数

それでは、Promiseを使ってsleep関数を実装してみましょう。

こんな感じで書けます。

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function main() {
  console.log("処理開始");

  await sleep(3000);
  console.log("3秒後に実行されます");

  await sleep(2000);  
  console.log("さらに2秒後に実行されます");

  console.log("処理終了");
}

main();

実行結果は次のようになります。

処理開始
3秒後に実行されます
さらに2秒後に実行されます
処理終了

sleep関数の中でPromiseを使っています。

Promiseのコンストラクタに渡した関数の中で、setTimeoutを使って指定時間経過後にresolveを呼び出しています。

そして、async/awaitを使ってsleep関数の完了を待つようにしているんですね。これにより、同期的な処理のように記述できるようになります。

○Promiseを使う際の注意点

Promiseを使ったsleep関数の実装は、setTimeoutを直接使うよりも見通しが良くなります。

ただ、注意点もあります。

Promiseを使った場合、エラーハンドリングを適切に行わないと、エラーが発生しても気づきにくいんです。

Promiseのコンストラクタ内でエラーが発生した場合は、rejectを呼び出してエラー状態にする必要があります。

そして、Promise.catchを使ってエラーをキャッチするようにしましょう。

また、古いブラウザだとPromiseがサポートされていない場合があります。

その場合は、ポリフィルを使ってPromiseを使えるようにする必要があるので注意しましょう。

でも、Promiseを使えば、非同期処理を扱いやすくなるのは間違いありません。

次は、もう1歩進んだasync/awaitを使ったsleep関数の実装を見ていきましょう。

●async/awaitを使ったsleep関数の実装

JavaScriptでsleep関数を実装する最も現代的な方法が、async/awaitを使う方法です。

async/awaitはPromiseをベースにした構文で、非同期処理を同期的に記述できるようにしてくれるんですよ。

○サンプルコード5:async/awaitの基本的な使い方

まずは、async/awaitの基本的な使い方から見ていきましょう。

こんな感じで書けます。

function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function asyncFunction() {
  console.log("非同期処理開始");
  await wait(3000);
  console.log("3秒後に実行されます");
  return "非同期処理完了";
}

asyncFunction().then(result => {
  console.log(result);
});

実行結果はこうなります。

非同期処理開始
3秒後に実行されます
非同期処理完了

asyncFunctionはasyncキーワードを付けた関数で、内部ではawaitを使って非同期処理の完了を待っています。

awaitを使うことで、Promiseの完了を待つことができるんですね。

そして、asyncFunctionの戻り値はPromiseになるので、その完了後の処理を.thenで記述しています。

○サンプルコード6:async/awaitを使ったsleep関数

それでは、async/awaitを使ってsleep関数を実装してみましょう。

先ほどのPromiseを使った例とほとんど同じですが、もう少しスッキリ書けます。

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function main() {
  console.log("処理開始");

  await sleep(3000);
  console.log("3秒後に実行されます");

  await sleep(2000);
  console.log("さらに2秒後に実行されます");

  console.log("処理終了");
}

main();

実行結果は次のようになります。

処理開始
3秒後に実行されます
さらに2秒後に実行されます
処理終了

sleep関数の実装は先ほどと同じですが、main関数をasync関数にして、sleep関数の呼び出しにawaitを付けているのがポイントです。

これにより、sleep関数の完了を待ってから次の処理に進むようになるんですね。

○async/awaitを使う際の注意点

async/awaitを使えば、非同期処理を同期的に記述できるので、コードの見通しが良くなります。

ただ、いくつか注意点もあります。

1つは、await を使うためには、その関数自体がasync関数である必要があることです。

そのため、トップレベルのコードでawaitを使うことはできません。

また、async/awaitを使ったからといって、処理が完全に同期的になるわけではありません。

あくまで、非同期処理の完了を待ってから次の処理を実行するだけです。

そのため、UIスレッドをブロックしないよう注意が必要です。

そして、エラーハンドリングにも気を付ける必要があります。

async関数内で発生したエラーは、通常のtry/catchでは捕捉できないので、.catchを使ってエラーをハンドリングする必要があるんですよ。

●よくあるエラーと対処法

JavaScriptでsleep関数を実装する際によく遭遇するエラーとその対処法について見ていきましょう。

sleep関数を使っていると、思わぬところでエラーが発生することがあるんですよね。

でも大丈夫、ここで紹介する方法を知っていれば、エラーに立ち向かえるはずです。

○エラー1:sleep中に処理が止まらない

まず、sleep関数を使っているのに、処理が止まらないというエラーがあります。

こんなコードを書いたことはありませんか。

function sleep(ms) {
  const start = new Date().getTime();
  while (new Date().getTime() - start < ms) {
    // 待機
  }
}

console.log("処理開始");
sleep(3000);
console.log("処理終了");

このコードを実行すると、処理開始と処理終了がほぼ同時に表示されてしまいます。

なぜでしょうか。

実は、このsleep関数は同期的に処理を止めているだけで、非同期処理ではないんですね。

そのため、sleep関数が完了するまで、その後の処理が進まないのです。

この問題を解決するには、先ほど紹介したsetTimeoutやPromise、async/awaitを使ったsleep関数を使う必要があります。

非同期処理にすることで、sleep中も他の処理を進められるようになるんですよ。

○エラー2:Promiseのエラーハンドリング

次に、Promiseを使ったsleep関数でよく発生するエラーを見てみましょう。

こんなコードはどうでしょうか。

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

sleep(3000).then(() => {
  console.log("スリープ完了");
}).catch(() => {
  console.log("エラーが発生しました");
});

このコードでは、sleep関数が正常に完了した後に、”スリープ完了”とログに出力されるはずです。

でも、実際に実行してみると、エラーが発生してもcatchが呼ばれることはありません。

これは、sleep関数内でエラーが発生していないからなんですね。

Promiseのcatchは、Promiseがrejectされたときに呼ばれるのですが、このsleep関数ではrejectが呼ばれることがないんです。

エラーをキャッチするためには、sleep関数内で意図的にエラーを発生させる必要があります。

例えば、こんな感じです。

function sleep(ms) {
  return new Promise((resolve, reject) => {
    if (ms < 0) {
      reject(new Error("時間は0以上の値を指定してください"));
    } else {
      setTimeout(resolve, ms);
    }
  });
}

sleep(-1000).then(() => {
  console.log("スリープ完了");
}).catch(error => {
  console.log(`エラーが発生しました: ${error.message}`);
});

ここでは、指定時間が負の値だった場合にPromiseをrejectするようにしています。

こうすることで、エラーが発生したときにcatchが呼ばれるようになるんですね。

○エラー3:async関数内のエラー

最後に、async/awaitを使ったsleep関数でのエラーについて見てみましょう。

次のようなコードがあるとします。

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function main() {
  console.log("処理開始");
  await sleep(3000);
  throw new Error("意図的なエラー");
  console.log("処理終了");
}

main();

このコードでは、sleep関数の呼び出し後に意図的にエラーを発生させています。

でも、このエラーはcatchされずにそのままプログラムが終了してしまいます。

これは、async関数内で発生したエラーは、通常のtry/catchでは捕捉できないからなんですね。

async関数は常にPromiseを返すので、エラーをキャッチするためには、Promiseのcatchを使う必要があります。

こんな感じにすれば、エラーをキャッチできるようになります。

async function main() {
  try {
    console.log("処理開始");
    await sleep(3000);
    throw new Error("意図的なエラー");
    console.log("処理終了");
  } catch (error) {
    console.log(`エラーが発生しました: ${error.message}`);
  }
}

main().catch(error => {
  console.log(`Promiseがキャッチしたエラー: ${error.message}`);
});

ここでは、async関数内でtry/catchを使ってエラーをキャッチしています。

さらに、async関数が返すPromiseのcatchでもエラーを処理しているんですね。

こうすることで、async関数内のエラーを確実に処理できるようになります。

●sleep関数の応用例

ここまでは、JavaScriptでsleep関数を実装する方法について詳しく見てきました。

でも、sleep関数ってどんなときに使うのが良いのでしょうか。

ここからは、sleep関数の具体的な応用例をいくつか紹介していきますね。

○サンプルコード7:ボタンクリック後に一定時間処理を待つ

まずは、ボタンクリック後に一定時間処理を待つという例から見ていきましょう。

こんなことしたくなることありますよね。

<button id="myButton">クリックしてね</button>
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

const button = document.getElementById("myButton");
button.addEventListener("click", async function() {
  console.log("ボタンがクリックされました");
  await sleep(3000);
  console.log("3秒後に実行されます");
});

ここでは、ボタンがクリックされたら、まず「ボタンがクリックされました」とログに出力しています。

そして、sleep関数で3秒間待機した後、「3秒後に実行されます」と出力しているんですね。

このように、ボタンクリック後の処理を遅らせたいときに、sleep関数が役立ちます。

例えば、フォームの送信ボタンを押した後、すぐに次のページに遷移するのではなく、「送信中…」と表示して一定時間待つような場面で使えそうですよね。

○サンプルコード8:一定間隔で処理を繰り返す

次は、一定間隔で処理を繰り返す例を見てみましょう。

こんな感じで書けます。

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function countdown(seconds) {
  for (let i = seconds; i > 0; i--) {
    console.log(i);
    await sleep(1000);
  }
  console.log("カウントダウン終了!");
}

countdown(5);

ここでは、countdownという関数を定義しています。

この関数は、指定された秒数からカウントダウンを始め、0になったら終了します。

カウントダウンの間は、1秒ごとに現在の数字を表示しているんですね。

sleep関数を使うことで、1秒ごとの間隔を作っています。

forループの中でawaitを使っているのがポイントです。

こうすることで、非同期処理を同期的に記述できるんですよ。

このように、一定間隔で処理を繰り返したいときにも、sleep関数が活躍します。

例えば、簡単なアニメーションを作るときや、一定時間ごとにサーバーにリクエストを送るようなときに使えそうですね。

○サンプルコード9:非同期処理の順次実行

最後は、非同期処理を順番に実行する例です。

非同期処理を順番に実行したいことってありますよね。

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function asyncTask1() {
  console.log("タスク1開始");
  await sleep(1000);
  console.log("タスク1完了");
  return 1;
}

async function asyncTask2(result) {
  console.log(`タスク2開始。タスク1の結果は${result}`);
  await sleep(1000);  
  console.log("タスク2完了");
  return result + 1;
}

async function asyncTask3(result) {
  console.log(`タスク3開始。タスク2の結果は${result}`);
  await sleep(1000);
  console.log("タスク3完了");  
  return result + 1;
}

async function main() {
  const result1 = await asyncTask1();
  const result2 = await asyncTask2(result1);
  const result3 = await asyncTask3(result2);
  console.log(`最終結果は${result3}`);
}

main();

ここでは、asyncTask1、asyncTask2、asyncTask3という3つの非同期関数を定義しています。

それぞれの関数は、1秒のスリープの後に完了し、その結果を返します。

そして、main関数の中で、これらの非同期関数を順番に呼び出しているんですね。

awaitを使うことで、ある非同期処理が完了するのを待ってから、次の処理を実行しています。

まとめ

JavaScriptには標準のsleep関数がありませんが、setTimeout、Promise、async/awaitを使えば、同等の処理を実装できることがわかりましたね。

sleep関数は、一定時間処理を止めたいときや、非同期処理を同期的に記述したいときに役立ちます。

ただ、実装する際にはいくつか注意点があります。

setTimeoutを直接使うと、コードの見通しが悪くなったり、UIスレッドをブロックしてしまったりするかもしれません。

また、Promiseやasync/awaitを使う場合は、エラーハンドリングを適切に行う必要があります。

でも、これらの注意点を理解していれば、sleep関数は非常に便利なツールになるはずです。

ボタンクリック後の処理を遅らせたり、一定間隔で処理を繰り返したり、非同期処理を順番に実行したりと、様々な場面で活躍してくれるでしょう。

みなさんも、JavaScriptでsleep関数が必要になったら、ここで紹介した方法を試してみてください。