setIntervalでawaitが効かない
setInterval
はPromise
のオブジェクトではないため、await
させて実行終了を待つことが出来ません。例えば、3秒毎にカウントを1ずつ増やしていく処理をsetInterval
で実装をすると以下のようになります。
const sleep = (ms) => setTimeout(() => undefined, ms); const intervalCount = (ms) => { let count = 0; setInterval(() => { console.log("[Info] count: ", count + 1) count++; }, ms); } console.log("start!!"); // 1秒毎にカウント intervalCount(1000); // 10カウントするまで待機 sleep(10 * 1000); console.log("finish!!");
10回分のカウントが終了するまで待機させていますが、先にstart!!
とfinish!!
のlogが表示されてしまいます。またsetInterval
に渡されたcallback
が動いたままになっている事も確認出来ます。並行に処理されているようです。
start!! finish!! [Info] count: 1 [Info] count: 2 [Info] count: 3 : [Info] count: 10 [Info] count: 11 :
想定していた出力は以下でした。
start!! [Info] count: 1 [Info] count: 2 [Info] count: 3 : [Info] count: 10 finish!! [Info] count: 11 :
Promiseを使って書き換える
Promise
を使うことで、先ほどのコードを想定通りの出力に変更することが出来ます。なぜPromise
を使うかというと、await
させる必要があるからです。
以下のようにすればオリジナルでinterval
関数を作成することで、待機可能なインターバルを実装することが出来ます。インターバルと言っていますが、実際にはwhile
をただ書いているだけです。
const interval = async (ms) => { let count = 0; while (count < 10) { await new Promise(resolve => setTimeout(resolve, ms)); console.log("[Info] count: ", count + 1); count++; } } // intervalをawaitさせるためにasyncが必要なため即時関数に (async () => { console.log("start!!"); await interval(1000); console.log("finish!!"); })();
上記を実行すると以下のようになります。
start!! [Info] count: 1 [Info] count: 2 [Info] count: 3 : [Info] count: 9 [Info] count: 10 finish!!
どちらも期待通りに出力されるようになりました。考えられる拡張としてはwhile
の停止条件を変えたい(eg: 10回ではなく20回にしたい)ということが考えられますが、これは引数を増やして渡すだけで簡単に対応することが出来ます。
const interval = async (ms, maxCount) => { let count = 0; while (count < maxCount) { await new Promise(resolve => setTimeout(resolve, ms)); console.log("[Info] count: ", count + 1); count++; } }
もっと複雑な条件を適応させたい場合は、条件を関数のまま渡すということも可能です。
const interval = async (ms, stopCond) => { let count = 0; while (stopCond()) { await new Promise(resolve => setTimeout(resolve, ms)); console.log("[Info] count: ", count + 1); count++; } } // intervalをawaitさせるためにasyncが必要なため即時関数に (async () => { console.log("start!!"); // 80%の確率で停止するインターバル const stopCond = () => Math.random() > 0.8; await interval(1000, stopCond); console.log("finish!!"); })();
以上です。以下はおまけになります。
おまけ: インターバルの間隔を変化させる
インターバル間隔を管理する初期変数を追加して、インクリメントされるcount
を利用して徐々にスリープ時間を長くするようにします。以下のコードでは静的に0.5秒ずつスリープ時間が増えていきます。
const interval = async (ms, maxCount) => { let count = 0; let intervalMs = 1000; while (count < maxCount) { const sleepMs = intervalMs + (count * 500); await new Promise(resolve => setTimeout(resolve, sleepMs)); console.log("[Info] count: ", count + 1); count++; } } (async () => { console.log("start!!"); await interval(1000, 10); console.log("finish!!"); })();
インターバル間隔の管理をinterval
関数の内部に持たせるのが嫌な場合は外部で関数で管理させるようにすることが可能です。
const interval = async (ms, maxCount, accelerator) => { let count = 0; while (count < maxCount) { const sleepMs = accelerator(count); await new Promise(resolve => setTimeout(resolve, sleepMs)); console.log("[Info] count: ", count + 1); count++; } } (async () => { console.log("start!!"); const accelerator = (count) => 1000 + count * 100; await interval(1000, 10, accelerator); console.log("finish!!"); })();
accelerator
でどれぐらい加速させるかを定義しています。もちろん、減速させることも可能です。この処理を使って、ビンゴのドラムロール式のルーレットを作りました。
無料で使えるビンゴを公開しています。忘年会シーズンなどぜひご活用ください。