setIntervalが使いにくい
setInterval
はPromise
のオブジェクトではないため、await
させて実行終了を待つことが出来ません。例えば、3秒毎にカウントを1ずつ増やしていく処理をsetInterval
で実装をすると以下のようになります。
const sleep = (ms) => { setTimeout(() => undefined, ms); }; const intervalCount = (ms) => { let counter = 0; setInterval(() => { console.log("[Info] count: ", counter) counter++; }, ms); } const main = () => { const intervalMs = 3000; console.log("start!!"); // 3秒毎にカウント intervalCount(3000); // 10カウントするまでwait sleep(intervalMs * 10); console.log("finish!!"); } main();
10回分のカウントが終了するまで待機させていますが、先にstart!!
とfinish!!
のlogが表示されてしまいます。またsetInterval
に渡されたcallback
が動いたままになっている事も確認出来ます。並行に処理されているようです。
start!! finish!! Hint: hit control+c anytime to enter REPL. [Info] count: 0 [Info] count: 1 [Info] count: 2 [Info] count: 3 [Info] count: 4 [Info] count: 5 [Info] count: 6 [Info] count: 7 [Info] count: 8 [Info] count: 9 [Info] count: 10 [Info] count: 11 [Info] count: 12 [Info] count: 13 : :
Promiseを使って書き換える
こちらをシリアルに実行されるようにPromise
を使ってawait
可能なように書き換えました。以下のようにすれば待機可能なインターバルを実装することが出来ます。拡張性を保持するために、callback
とインターバルの継続条件(continueCondition as continueCond
)は引数にて関数を受け取るようにしました。
const interval = async (ms, callback, continueCond) => { const _interval = async () => { await new Promise(resolve => { setTimeout(resolve, ms); }); if (continueCond()) { callback(); await _interval(); } }; await _interval(); } // example const main = async () => { const intervalMs = 3000; console.log("start!!"); // 3秒毎にカウント let count = 0; const callback = (counter) => { console.log("[Info] count: ", counter); count++; }; const continueCond = (counter) => 10 > counter; await interval( 3000, () => callback(count), () => continueCond(count), ); console.log("finish!!"); }
上記を実行すると以下のようになります。
start!! [Info] count: 0 [Info] count: 1 [Info] count: 2 [Info] count: 3 [Info] count: 4 [Info] count: 5 [Info] count: 6 [Info] count: 7 [Info] count: 8 [Info] count: 9 finish!!
期待通りに出力されるようになりました。拡張性を確保するために、クロージャにlet
で定義した変数を渡しているため、ややこしくなってしまっています。
拡張性をなくして、シンプルな実装にすると非常に簡単になります。あまり使い回すような処理ではないと思うので、基本ベタ書きで良さそうです。実行結果は先ほどと同じになります。
const intervalCount = async (ms, maxCnt) => { let count = 0; const _intervalCount = async () => { await new Promise(resolve => { setTimeout(resolve, ms); }); if (maxCnt > count) { console.log("[Info] count: ", count); count++; await _intervalCount(); } }; await _intervalCount(); } // main function const main = async () => { const intervalMs = 3000; console.log("start!!"); await intervalCount(intervalMs, 10); console.log("finish!!"); } main();
以上です。以下はおまけになります。
応用: インターバルの間隔を変化させる
(元々、これがやりたかった)
第一引数にて受け取っていたms
の固定値を関数として受け取りクロージャを利用して値を変化させるようにしました。
徐々にインターバルの間隔を遅くしていく場合には、accelerator
にて値を加算させるようにします。早くしたい場合は減算させます。
const intervalMs = 1000; let count = 0; const accelerator = (ms, counter) => ms + (counter * 500) const callback = (counter) => { console.log("[Info] count: ", counter); count++; }; const continueCond = (counter) => 10 > counter; await interval( () => accelerator(intervalMs, count), () => callback(count), () => continueCond(count), ); console.log("finish!!");
start!! sleepTime: 1000 [Info] count: 0 sleepTime: 1500 [Info] count: 1 sleepTime: 2000 [Info] count: 2 sleepTime: 2500 [Info] count: 3 sleepTime: 3000 : :
React.js
でランダム値を出力するcomponentを作るのに使いました。