こんにちは! Webコーダー・プログラマーの貝原(@touhicomu)です。
JavaScriptを使っていると処理を一時停止し、ある一定時間経過した後でまた再開したいときがありますよね。他のプログラミング言語を使ったことがある方は、sleepを使えばできるだろうと考えるでしょう。
実は、JavaScriptにはsleepというメソッドが存在していません。でもご安心ください。JavaScriptにもsleep機能を実現する方法はあります。
下記の流れで、JavaScriptでsleep機能を実現する方法を確認していきましょう。
【基礎】ビジーwaitを利用したsleep機能を作る
【基礎】タイマー(timer)を活用する
【発展】Promiseでsleep機能を作る
【発展】jQueryでsleep機能を作る
【発展】async / awaitでsleep機能を作る
他の言語とちょっと使い勝手は違うかもしれませんが、慣れてしまえばなんということはありません。初めてsleep機能を使うという方も、サンプルコードをしっかり確認して学習していってくださいね。
sleepとは
「sleep」とはプログラムの実行を一時停止するものです。
主にマルチスレッドや無限ループなどで、メインの処理から分岐し処理を停止させることで、CPUの負荷を抑える目的で使われています。メイン処理の負荷が納まった頃にsleep処理から復帰することで、CPU負荷に余裕を持って処理を再開させることができます。
sleepには主にOSがsleep機能を提供している場合と、言語環境自身が提供してる場合もあります。
JavaScriprtは後者であり、さらにsleep機能を実現する関数はありません。用意された関数を使ってsleep機能を実現するプログラムを作る必要があります。
なのでこの記事で紹介する方法は厳密なsleepではなく、擬似的に動くsleepであることを覚えておいてください。
ビジーwaitを利用したsleep機能を作る
sleep機能を実現する一つの方法として、while()ループを指定秒の間「空ループ」させるやり方があります。
これは、開始時刻を記録しておいてそこから指定秒数経ったらwhile()ループを抜けるという手法です。ただし、このやり方だと常にCPUを消費して「ビジー状態」になるという点に注意が必要です。
次のサンプル例を見てください!
// ビジーwaitを使う方法 function sleep(waitMsec) { var startMsec = new Date(); // 指定ミリ秒間だけループさせる(CPUは常にビジー状態) while (new Date() - startMsec < waitMsec); } sleep(5000); // 5秒後にメッセージを表示 console.log('5秒経過しました!');
実行結果
5秒経過しました!
startMsecに開始時刻を記録し、while()ループの中で常に現在時刻new Date()との差をとります。その差が指定秒数以上になったらwhile()ループを抜けることで5秒間スリープするというわけです。
タイマー(timer)を活用する
この章では、タイマー処理を活用したsleep機能を実現する方法について見ていきましょう! 主に、「setInterval()」「setTimeout()」を利用したプログラミング手法について学んでいきます。
setInterval()を使ったsleep機能を作る
まずは、「setInterval()」を使ったsleep機能の作り方から見ていきましょう!
setIntervalは指定ミリ秒間隔で同じメソッドの実行を繰り返すものです。例として、1秒経って起動された関数内で経過時間をカウントし、経過時間が指定秒数以上経ったときに繰り返し処理を完了するコードを書いてみましょう。
// setIntervalを使う方法 function sleep(waitSec, callbackFunc) { // 経過時間(秒) var spanedSec = 0; // 1秒間隔で無名関数を実行 var id = setInterval(function () { spanedSec++; // 経過時間 >= 待機時間の場合、待機終了。 if (spanedSec >= waitSec) { // タイマー停止 clearInterval(id); // 完了時、コールバック関数を実行 if (callbackFunc) callbackFunc(); } }, 1000); } sleep(5, function () { console.log('5秒経過しました!'); });
このように、setInterval()に1000ミリ秒を指定し、1秒間隔で「無名関数」を実行しています。無名関数では経過時間をカウントし、指定秒数経過した場合にclearInterval()でタイマーを停止し待機を完了してます。
sleep()にはコールバック関数を与えており、指定秒数経過したあとすぐに実行される仕組みです。ただ、このコールバック関数はメインの処理とは別に並列で実行されるため待機することが無いのが特徴です。
「setInterval()」の使い方については、下記の記事で詳しく解説しているのでぜひ参考にしてください。
setTimeout()を使ったsleep機能を作る
次に、setTimeout()を使ったsleep機能を作成してみましょう! 1秒後に起動された関数内で経過時間をカウントし、再びsetTimeoutで同じ関数を登録します。
これで、1秒ごとに繰り返し同じ関数が呼び出されます。その後、カウントしている経過時間が指定秒数以上経ったときに繰り返し処理を完了します。
function sleep(waitSec, callbackFunc) { var spanedSec = 0; var waitFunc = function () { spanedSec++; if (spanedSec >= waitSec) { if (callbackFunc) callbackFunc(); return; } clearTimeout(id); id = setTimeout(waitFunc, 1000); }; var id = setTimeout(waitFunc, 1000); } sleep(5, function() { console.log('5秒経過しました!'); });
以上のように、setTimeout()に1000ミリ秒を指定して1秒後にwaitFunc()を実行しています。呼び出されたwaitFunc()内にて再びwaitFunc()をsetTimeout()に登録しています。
これで、1秒間隔でwaitFunc ()が呼び出されます。waitFunc()では経過時間をカウントし、指定秒数経過した場合にclearTimeout()でタイマーを停止して待機を完了してます。
setTimeoutについてもっと詳しく知りたい方は、下記の記事も参考にしてみてください。
Promiseでsleep機能を作る
sleep機能を実現する別の方法として、ECMAJavaScript2015(ES6)から導入されたPromiseを使う方法があります。
Promiseは、コールバック関数を受け取り、引数を渡してコールバック関数を実行します。コールバック関数内でPromiseのresove()メソッドを呼び出すとPromiseが正常終了します。このコールバック関数内でsetTimeoutにresolveメソッドを登録しておきます。
window.onload = function () { // Promiseを使う方法 function sleepByPromise(sec) { return new Promise(resolve => setTimeout(resolve, sec*1000)); } // async修飾子を使って非同期関数を宣言します。 async function wait(sec) { console.log('wait ' + sec.toString() + ' sec right now!'); // await句を使って、Promiseの非同期処理が完了するまで待機します。 await sleepByPromise(sec); console.log('wait ' + sec.toString() + ' sec done!'); } console.log('anothor task 1 '); console.log('anothor task 2 '); wait(5); console.log('anothor task 3 '); console.log('anothor task 4 '); }
実行結果
anothor task 1 anothor task 2 wait 5 sec right now! anothor task 3 anothor task 4 wait 5 sec done!
Promiseはawait句を使って呼び出されると、処理はPromiseが完了するまで待機します。
また、wait()には、async修飾子をつけているため、メインの処理とは並列に実行されます。ただし、wait()内ではPromiseの待機はawaitにより可能になります。
実行結果をみてみると、wait()呼び出しによるメイン処理の待機はできていません。これは、async修飾子によりwait()がメイン処理と並列に実行されるからです。
また、Promiseはawaitを使って待機しています。これはメイン処理とは別個に並列処理されているなかでの待機です。
その後、setTimeoutにより5秒後にresolve()が実行されてます。その結果、5秒後に、“wait 5 sec done!”と表示されています。
Promiseがどのようなものなのか、どのように使うのかは下記の記事で詳しく解説しています。気になる方は、ぜひ参考にしてください。
jQueryでsleep機能を作る
jQueryは、JavaScriptのライブラリのひとつです。普通のJavaScriptでできる動きが色々まとめられており、初心者にも扱いやすいものとなっています。
jQueryを使うためには、jQuery自体を読み込む必要があります。
読み込み方については、下記の記事で詳しく解説しているのでぜひ参考にしてみてください。
このjQueryの場合、Deferredオブジェクトを使うことでsleep機能を実装できます。
DeferredオブジェクトはPromiseを返します。そのまま待機時間完了後にresolve()メソッドを実行すると、Deferredの処理が完了し、次の処理としてメソッドチェーンで繋いでいるdone()メソッドが実行されます。
例を見てみましょう。
window.onload = function () { // jQueryでSleepする方法 function wait(sec) { // jQueryのDeferredを作成します。 var objDef = new $.Deferred; setTimeout(function () { // sec秒後に、resolve()を実行して、Promiseを完了します。 objDef.resolve(sec); }, sec*1000); return objDef.promise(); }; console.log('wait 5sec right now!'); // waitはPromiseを返すので、doneをメソッド・チェーンできます。 wait(5).done(function () { console.log('5sec spaned!'); }); }
実行結果
wait 5sec right now! 5sec spaned!
以上のようにwait()で5秒待機するよう指定すると、指定時間後にPromiseオブジェクトが返されるため、メソッドチェーンにてdone()メソッドを実行できます。また、done()メソッドに無名関数を与えると、5秒後に実行されます。
このwait()による待機もメイン処理とは並列に行なわれます。
async/awaitでsleep機能を作る
最後に、新しいJavaScriptであるES2017より使えるようになった「async / await」を利用したsleep機能の実現方法についても見ていきましょう! Promiseによる非同期処理とタイマー処理を組み合わせることで、とても効率よく処理を記述することが可能です。
次のサンプル例を見てください!
function sleep(msec) { return new Promise(function(resolve) { setTimeout(function() {resolve()}, msec); }) }
これは単純に指定時間経過後にPromiseの「resolve」だけを返すという処理になっています。そして「async / await」を使って次のように記述します!
async function start() { await sleep(5000); console.log('5秒経過しました!'); } start();
「await sleep(5000)」と記述することで、引数に指定したミリ秒が経過するまで処理を一時停止するというわけです。指定時間経過後は「resolve」が返ってくるので、そのまま次の処理に移行することになります。
ちなみに、「async / await」について基本から応用技までを次の記事でまとめているので参考にしてみてください!
まとめ
いかがでしたか?
JavaScriptにはsleep関数はありません。しかし、ビジーwaitやsetInterval、setTimeout、Primise、jQueryなどを使用することにより、sleepの機能を実現することが可能です。
ただし、ビジーwait以外はメイン処理とは並列に実行されるサブ処理であり、メイン処理は待機しないことをお忘れなく。
sleep機能はプログラムの処理フローを制御する場合によく使用する機能です。
うっかり忘れてしまったら、またこの記事で確認してみてくださいね!