2019-05-26に投稿

TypeScriptでsetTimeout()がNodeJS.Timerになる理由から、window.setTimeout()との違いを理解する

AngularのSPA(Single Page Application)のプログラムを書いてたら、以下の問題に遭遇しました。

内容としては「Type 'Timer' is not assignable to type 'number'.」というエラーメッセージが表示されます。
VSCode(Visual Studio Code)で関数の戻り値を確認すると、戻り値の型はNodeJS.Timerになってます。

私の場合は、Angularで「setTimeout()」を記述しただけです。なぜそれが「NodeJS.Timer」となってしまうのか気になったので、調べてみました。

window.setTimeout()にするとnumberになる

次のstack overflowを見るとわかるのですが、「setTimeout()」を「window.setTimeout()」に変えると、戻り値は数値型で通用します。

// Another workaround that does not affect variable declaration:
let n: number;
n = <any>setTimeout(function () { /* snip */  }, 500);

// Also, it should be possible to use the window object explicitly without any:
let n: number;
n = window.setTimeout(function () { /* snip */  }, 500);

この仕様はMDNにも記載されており、「window.setTimeout()」のドキュメントを確認すると、確かに戻り値は数値型です。

戻り値 timeoutID は、setTimeout() を呼び出して作成したタイマーを識別する正の整数値です。

引用:https://developer.mozilla.org/ja/docs/Web/API/WindowTimers/setTimeout

setTimeout()とwindow.setTimeout()の違い

ではなぜ「window.setTimeout()」にするとTypeScriptの戻り値の解釈が変わるのかと申しますと、これは「setTimeout()」との違いが関係するようです。

For JavaScript that does not run in a browser, the window object is not defined, so window.setTimeout() will fail. setTimeout() however, will work.

引用: https://stackoverflow.com/questions/20420429/what-the-difference-between-window-settimeout-and-settimeout

function myF() {
  function setTimeout(callback,seconds) {
    // call the native setTimeout function
    return window.setTimeout(callback,seconds*1000);
  }
  // call your own setTimeout function (with seconds instead of milliseconds)
  setTimeout(function() {console.log("hi"); },3);
}
myF();

要約すると「window.setTimeout()」が明示的にブラウザの「setTimeout()」を使うよう指示していることに対し、
単なる「setTimeout()」ではブラウザ外(例えばNode.jsのサーバーサイド)のJSや、自分たちで用意した関数も考慮されます。

その結果「setTimeout()」のTypeScriptにおける戻り値の型が、NodeJS.Timerになっているようです。

20190526144410.jpg

「window.」の省略について

JavaScriptでは、グローバルオブジェクトの「window.」の記述を省略できます。

  • 「window.location.href」を「location.href」と書けます
  • 「window.alert()」は「alert()」と書けます

ただし私が遭遇した今回の事例は、まさに「window.」を省略したことによって起こりました。
調べてみると、確かにNode.jsにはTimerのAPIで「setTimeout()」関数があります。

「window.」を省略したことで、TypeScriptがNode.jsの「setTimeout()」を使うと判断したという理屈になります。

さいごに

色々と調べてみた結果ですが、私の中では以下の結論に落ち着きました。

  • 確実にブラウザの「setTimeout()」を動かしたいなら、「window.」は省略しないほうが良さそう
  • ブラウザとサーバーサイドの両方で動作を想定するなら、「window.」は省略して記述する
Originally published at www.konosumi.net
ツイッターでシェア
みんなに共有、忘れないようにメモ

このすみ

エンジニア。ブログで、映画・ゲーム・読書の感想を書いたり、技術系の知見を発信しています。 技術書典5で「エンジニアアンチパターン」と「PHP中級者を目指す 〜言語を使いこなすための本〜」という本を出しました。

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント