JavaScriptタイマーの探求

setTimeout()

JavaScriptコードを書いていると、関数の実行を遅らせたいことがあるかもしれません。

これがsetTimeoutの仕事です。後で実行するコールバック関数と、それをどれくらい後に実行したいかを示す値をミリ秒単位で指定します。

(() => {
  // runs after 2 seconds
}, 2000);

(() => {
  // runs after 50 milliseconds
}, 50);

この構文は新しい関数を定義します。その中で好きな他の関数を呼び出すこともできますし、既存の関数名とパラメータのセットを渡すこともできます。

const  = (, ) => {
  // do something
};

// runs after 2 seconds
(, 2000, firstParam, secondParam);

setTimeoutはNode.jsではTimeoutインスタンスを返しますが、ブラウザでは数値のタイマーIDを返します。このオブジェクトまたはIDを使用して、スケジュールされた関数の実行をキャンセルできます。

const  = (() => {
  // should run after 2 seconds
}, 2000);

// I changed my mind
();

遅延ゼロ

タイムアウト遅延を0に指定すると、コールバック関数は可能な限り早く、しかし現在の関数実行の後に実行されます。

(() => {
  .('after ');
}, 0);

.(' before ');

このコードは次のように出力します

before
after

これは、集中的なタスクでCPUをブロックするのを避け、重い計算を実行中に他の関数を実行させるために特に便利です。スケジューラに関数をキューイングすることで実現します。

一部のブラウザ(IEとEdge)では、これと全く同じ機能を持つsetImmediate()メソッドが実装されていますが、これは標準ではなく、他のブラウザでは利用できません。しかし、Node.jsでは標準の関数です。

setInterval()

setIntervalsetTimeoutに似た関数ですが、違いがあります。コールバック関数を一度だけ実行する代わりに、指定した時間間隔(ミリ秒単位)で永遠に実行し続けます。

(() => {
  // runs every 2 seconds
}, 2000);

上の関数は、setIntervalが返したインターバルIDを渡してclearIntervalで停止を指示しない限り、2秒ごとに実行されます。

const  = (() => {
  // runs every 2 seconds
}, 2000);

();

setIntervalのコールバック関数内でclearIntervalを呼び出し、再度実行すべきか停止すべきかを自己判断させるのが一般的です。例えば、このコードはApp.somethingIWaitの値がarrivedになるまで何かを実行します。

const  = (() => {
  if (App.somethingIWait === 'arrived') {
    ();
  }
  // otherwise do things
}, 100);

再帰的setTimeout

setIntervalは、関数の実行が終了したかどうかに関係なく、nミリ秒ごとに関数を開始します。

関数が常に同じ時間を要する場合は、全く問題ありません。

setInterval working fine

しかし、例えばネットワークの状態によって、関数が異なる実行時間を要するかもしれません。

setInterval varying duration

そして、一つの長い実行が次の実行と重なってしまうかもしれません。

setInterval overlapping

これを避けるために、コールバック関数が終了したときに再帰的なsetTimeoutをスケジュールすることができます。

const  = () => {
  // do something

  (, 1000);
};

(, 1000);

このシナリオを実現するために

Recursive setTimeout

setTimeoutsetIntervalは、Node.jsのTimersモジュールを通じて利用できます。

Node.jsはsetImmediate()も提供しており、これはsetTimeout(() => {}, 0)を使用するのと同等で、主にNode.jsのイベントループと連携するために使用されます。