●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の基本的な使い方から見ていきましょう。
こんな感じで書けます。
実行結果はこうなります。
setTimeoutに渡した関数は、指定した時間(ミリ秒)が経過した後に実行されるんですね。
でも、その間もプログラムは止まらずに先に進んでいます。
これがsetTimeoutの非同期的な性質です。
○サンプルコード2:setTimeoutを使ったsleep関数
setTimeoutを使ってsleep関数っぽいものを作るには、ちょっとややこしいですが、こんな感じに書けます。
実行結果は次のようになります。
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の基本的な使い方から見ていきましょう。
こんな感じで書けます。
実行結果はこうなります。
Promiseのコンストラクタに渡した関数の中で、非同期処理(ここではsetTimeout)を行っています。
処理が完了したらresolveを呼び出して、Promiseを完了状態にします。
そして、promise.thenで完了後の処理を記述しているんですね。
○サンプルコード4:Promiseを使ったsleep関数
それでは、Promiseを使ってsleep関数を実装してみましょう。
こんな感じで書けます。
実行結果は次のようになります。
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の基本的な使い方から見ていきましょう。
こんな感じで書けます。
実行結果はこうなります。
asyncFunctionはasyncキーワードを付けた関数で、内部ではawaitを使って非同期処理の完了を待っています。
awaitを使うことで、Promiseの完了を待つことができるんですね。
そして、asyncFunctionの戻り値はPromiseになるので、その完了後の処理を.thenで記述しています。
○サンプルコード6:async/awaitを使ったsleep関数
それでは、async/awaitを使ってsleep関数を実装してみましょう。
先ほどのPromiseを使った例とほとんど同じですが、もう少しスッキリ書けます。
実行結果は次のようになります。
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関数を使っているのに、処理が止まらないというエラーがあります。
こんなコードを書いたことはありませんか。
このコードを実行すると、処理開始と処理終了がほぼ同時に表示されてしまいます。
なぜでしょうか。
実は、このsleep関数は同期的に処理を止めているだけで、非同期処理ではないんですね。
そのため、sleep関数が完了するまで、その後の処理が進まないのです。
この問題を解決するには、先ほど紹介したsetTimeoutやPromise、async/awaitを使ったsleep関数を使う必要があります。
非同期処理にすることで、sleep中も他の処理を進められるようになるんですよ。
○エラー2:Promiseのエラーハンドリング
次に、Promiseを使ったsleep関数でよく発生するエラーを見てみましょう。
こんなコードはどうでしょうか。
このコードでは、sleep関数が正常に完了した後に、”スリープ完了”とログに出力されるはずです。
でも、実際に実行してみると、エラーが発生してもcatchが呼ばれることはありません。
これは、sleep関数内でエラーが発生していないからなんですね。
Promiseのcatchは、Promiseがrejectされたときに呼ばれるのですが、このsleep関数ではrejectが呼ばれることがないんです。
エラーをキャッチするためには、sleep関数内で意図的にエラーを発生させる必要があります。
例えば、こんな感じです。
ここでは、指定時間が負の値だった場合にPromiseをrejectするようにしています。
こうすることで、エラーが発生したときにcatchが呼ばれるようになるんですね。
○エラー3:async関数内のエラー
最後に、async/awaitを使ったsleep関数でのエラーについて見てみましょう。
次のようなコードがあるとします。
このコードでは、sleep関数の呼び出し後に意図的にエラーを発生させています。
でも、このエラーはcatchされずにそのままプログラムが終了してしまいます。
これは、async関数内で発生したエラーは、通常のtry/catchでは捕捉できないからなんですね。
async関数は常にPromiseを返すので、エラーをキャッチするためには、Promiseのcatchを使う必要があります。
こんな感じにすれば、エラーをキャッチできるようになります。
ここでは、async関数内でtry/catchを使ってエラーをキャッチしています。
さらに、async関数が返すPromiseのcatchでもエラーを処理しているんですね。
こうすることで、async関数内のエラーを確実に処理できるようになります。
●sleep関数の応用例
ここまでは、JavaScriptでsleep関数を実装する方法について詳しく見てきました。
でも、sleep関数ってどんなときに使うのが良いのでしょうか。
ここからは、sleep関数の具体的な応用例をいくつか紹介していきますね。
○サンプルコード7:ボタンクリック後に一定時間処理を待つ
まずは、ボタンクリック後に一定時間処理を待つという例から見ていきましょう。
こんなことしたくなることありますよね。
ここでは、ボタンがクリックされたら、まず「ボタンがクリックされました」とログに出力しています。
そして、sleep関数で3秒間待機した後、「3秒後に実行されます」と出力しているんですね。
このように、ボタンクリック後の処理を遅らせたいときに、sleep関数が役立ちます。
例えば、フォームの送信ボタンを押した後、すぐに次のページに遷移するのではなく、「送信中…」と表示して一定時間待つような場面で使えそうですよね。
○サンプルコード8:一定間隔で処理を繰り返す
次は、一定間隔で処理を繰り返す例を見てみましょう。
こんな感じで書けます。
ここでは、countdownという関数を定義しています。
この関数は、指定された秒数からカウントダウンを始め、0になったら終了します。
カウントダウンの間は、1秒ごとに現在の数字を表示しているんですね。
sleep関数を使うことで、1秒ごとの間隔を作っています。
forループの中でawaitを使っているのがポイントです。
こうすることで、非同期処理を同期的に記述できるんですよ。
このように、一定間隔で処理を繰り返したいときにも、sleep関数が活躍します。
例えば、簡単なアニメーションを作るときや、一定時間ごとにサーバーにリクエストを送るようなときに使えそうですね。
○サンプルコード9:非同期処理の順次実行
最後は、非同期処理を順番に実行する例です。
非同期処理を順番に実行したいことってありますよね。
ここでは、asyncTask1、asyncTask2、asyncTask3という3つの非同期関数を定義しています。
それぞれの関数は、1秒のスリープの後に完了し、その結果を返します。
そして、main関数の中で、これらの非同期関数を順番に呼び出しているんですね。
awaitを使うことで、ある非同期処理が完了するのを待ってから、次の処理を実行しています。
まとめ
JavaScriptには標準のsleep関数がありませんが、setTimeout、Promise、async/awaitを使えば、同等の処理を実装できることがわかりましたね。
sleep関数は、一定時間処理を止めたいときや、非同期処理を同期的に記述したいときに役立ちます。
ただ、実装する際にはいくつか注意点があります。
setTimeoutを直接使うと、コードの見通しが悪くなったり、UIスレッドをブロックしてしまったりするかもしれません。
また、Promiseやasync/awaitを使う場合は、エラーハンドリングを適切に行う必要があります。
でも、これらの注意点を理解していれば、sleep関数は非常に便利なツールになるはずです。
ボタンクリック後の処理を遅らせたり、一定間隔で処理を繰り返したり、非同期処理を順番に実行したりと、様々な場面で活躍してくれるでしょう。
みなさんも、JavaScriptでsleep関数が必要になったら、ここで紹介した方法を試してみてください。