田舎で並行処理の夢を見る

試したこと、需要がないかもしれないけど細々とアウトプットしてます

JavaScriptでawait可能なintervalの書き方

setIntervalが割と使いにくい

setIntervalPromiseのオブジェクトではないため、awaitさせて実行終了を待つことが出来ません。例えば、3秒毎にカウントを1ずつ増やしていく処理をsetIntervalで実装をすると以下のようになります。

gist.github.com

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)は引数にて関数を受け取るようにしました。

gist.github.com

上記を実行すると以下のようになります。

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の固定値を関数として受け取りクロージャを利用して値を変化させるようにしました。

gist.github.com

徐々にインターバルの間隔を遅くしていく場合には、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を作るのに使いました。 f:id:takamizawa46:20210103161915g:plain

参考文献