TypeScript - utilisez la version correcte de setTimeout (node ​​vs window)

115

Je travaille sur la mise à niveau d'un ancien code TypeScript pour utiliser la dernière version du compilateur, et j'ai des problèmes avec un appel à setTimeout. Le code s'attend à appeler la setTimeoutfonction du navigateur qui renvoie un nombre:

setTimeout(handler: (...args: any[]) => void, timeout: number): number;

Cependant, le compilateur résout cela à la place de l'implémentation du nœud, qui renvoie un NodeJS.Timer:

setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer;

Ce code ne s'exécute pas dans le nœud, mais les types de nœud sont extraits en tant que dépendance à quelque chose d'autre (je ne sais pas quoi).

Comment puis-je demander au compilateur de choisir la version setTimeoutque je souhaite?

Voici le code en question:

let n: number;
n = setTimeout(function () { /* snip */  }, 500);

Cela produit l'erreur du compilateur:

TS2322: Le type 'Timer' n'est pas attribuable au type 'number'.

Kevin Tighe
la source
Avez-vous un type: ["node"] dans votre tsconfig.json? Voir stackoverflow.com/questions/42940954/…
koe
@koe Non, je n'ai pas l'option types: ["node"] dans le fichier tsconfig. Mais les types de nœuds sont intégrés en tant que dépendance npm à autre chose.
Kevin Tighe
1
Vous pouvez également définir explicitement des "types" dans tsconfig.json - lorsque vous omettez "node", il n'est pas utilisé dans la compilation. par exemple "types": ["jQuery"]
koe
1
Il est surprenant que la réponse de @koe (utiliser l'option "types") n'ait pas de votes, étant la seule vraie bonne réponse.
Egor Nepomnyaschih le
@ KevinTighe typesn'inclut pas nodemais setTimeoutobtient toujours son type de nœud plutôt que son type de navigateur. typespar défaut à tous les types de node_modules/@types, comme expliqué dans typescriptlang.org/tsconfig#types , mais même si vous ne spécifiez typeset ne pas inclure "node", pourquoi setTimeoutn'obtenez toujours son type de nœud et comment pouvez - vous obtenir le type de navigateur? La solution de @ Axke est un peu un hack, disant essentiellement qu'elle renvoie ce qu'elle renvoie. TypeScript peut encore trouver le mauvais type, mais au moins il sera toujours faux.
Denis Howe

Réponses:

88

Une autre solution de contournement qui n'affecte pas la déclaration de variable:

let n: number;
n = <any>setTimeout(function () { /* snip */  }, 500);

De plus, il devrait être possible d'utiliser l' windowobjet explicitement sans any:

let n: number;
n = window.setTimeout(function () { /* snip */  }, 500);
dhilt
la source
25
Je pense que l'autre ( window.setTimeout) devrait être la bonne réponse à cette question car c'est la solution la plus claire.
amik
5
Si vous utilisez le anytype, vous ne donnez pas vraiment de réponse TypeScript.
S ..
de même, le numbertype entraînera des erreurs de charpie spécifiques à TypeScript, car la setTimeoutfonction en nécessite plus.
S ..
1
window.setTimeoutpeut causer des problèmes avec les frameworks de tests unitaires (node.js). La meilleure solution est d'utiliser let n: NodeJS.Timeoutet n = setTimeout.
cchamberlain
110
let timer: ReturnType<typeof setTimeout> = setTimeout(() => { ... });

clearTimeout(timer);

En utilisant, ReturnType<fn>vous obtenez l'indépendance de la plate-forme. Vous ne serez pas obligé d'utiliser anyni window.setTimeoutqui ne cassera si vous exécutez le code no nodeJS server (par exemple, page de rendu côté serveur).


Bonne nouvelle, c'est aussi compatible avec Deno!

Akxe
la source
9
Je crois comprendre que c'est la bonne réponse et qu'elle devrait être acceptée, car elle fournit la bonne définition de type pour chaque plate-forme prenant en charge setTimeout/ clearTimeoutet n'utilisant pas any.
afenster
11
C'est la solution si vous écrivez une bibliothèque qui s'exécute à la fois sur NodeJS et sur le navigateur.
yqlim
Le type de retour est NodeJS.Timeoutsi vous utilisez setTimeoutdirectement et numbersi vous utilisez window.setTimeout. Ne devrait pas avoir besoin d'utiliser ReturnType.
cchamberlain
@cchamberlain Vous en avez besoin lorsque vous exécutez la setTimeoutfonction et attendez que son résultat soit stocké dans la variable. Essayez-le vous-même dans TS Playground.
Akxe
Pour l'OP et moi, TypeScript obtient le type de setTimeoutfaux (pour des raisons que personne ne peut expliquer) mais au moins cette solution devrait masquer cela d'une manière légèrement meilleure que la simple utilisation any.
Denis Howe
15

Je suppose que cela dépend de l'endroit où vous exécuterez votre code.

Si votre cible d'exécution est Node JS côté serveur, utilisez:

let timeout: NodeJS.Timeout;
global.clearTimeout(timeout);

Si votre cible d'exécution est un navigateur, utilisez:

let timeout: number;
window.clearTimeout(timeout);
cwouter
la source
5

Cela fonctionnera probablement avec les versions plus anciennes, mais avec la version TypeScript ^3.5.3et la version Node.js ^10.15.3, vous devriez être en mesure d'importer les fonctions spécifiques au nœud depuis le module Timers , c'est-à-dire:

import { setTimeout } from 'timers';

Cela renverra une instance de Timeout de type NodeJS.Timeoutque vous pouvez passer à clearTimeout:

import { clearTimeout, setTimeout } from 'timers';

const timeout: NodeJS.Timeout = setTimeout(function () { /* snip */  }, 500);

clearTimeout(timeout);
Nick Bernard
la source
1
De même, si vous voulez la version du navigateur de setTimeout, quelque chose comme const { setTimeout } = windoweffacera ces erreurs.
Jack Steam le
4

J'ai été confronté au même problème et la solution de contournement que notre équipe a décidé d'utiliser était simplement d'utiliser "any" pour le type de minuterie. Par exemple:

let n: any;
n = setTimeout(function () { /* snip */  }, 500);

Il fonctionnera avec les deux implémentations des méthodes setTimeout / setInterval / clearTimeout / clearInterval.

Mark Dolbyrev
la source
2
Ouais, ça marche. J'ai également réalisé que je pouvais simplement spécifier la méthode directement sur l'objet window: window.setTimeout (...). Je ne sais pas si c'est la meilleure façon de procéder, mais je vais m'en tenir pour le moment.
Kevin Tighe
1
Vous pouvez importer correctement l'espace de noms NodeJS dans un script dactylographié, voir cette réponse .
hlovdal
Pour réellement répondre à la question ("comment puis-je demander au compilateur de choisir la version que je veux"), vous pouvez utiliser window.setTimeout () à la place, comme @dhilt a répondu ci-dessous.
Anson VanDoren
window.setTimeoutpeut ne pas fonctionner avec les frameworks de tests unitaires. Il existe un type qui peut être utilisé ici .. Son NodeJS.Timeout. Vous pensez peut-être que vous n'êtes pas dans un environnement de nœuds mais j'ai des nouvelles pour vous: Webpack / TypeScript etc. exécutent node.js.
cchamberlain
1
  • Si vous voulez une vraie solution pour dactylographié sur les minuteries, nous y allons:

    Le bogue est dans le type de retour «nombre» ce n'est pas Timer ou autre chose.

    Ceci est pour la solution dactylographié ~> 2.7:

export type Tick = null | number | NodeJS.Timer;

Maintenant, nous avons tout corrigé, déclarez simplement comme ceci:

 import { Tick } from "../../globals/types";

 export enum TIMER {
    INTERVAL = "INTERVAL",
    TIMEOUT = "TIMEOUT", 
 };

 interface TimerStateI {
   timeInterval: number;
 }

 interface TimerI: TimerStateI {
   type: string;
   autoStart: boolean;
   isStarted () : bool;
 }

     class myTimer extends React.Component<TimerI, TimerStateI > {

          private myTimer: Tick = null;
          private myType: string = TIMER.INTERVAL;
          private started: boll = false;

          constructor(args){
             super(args);
             this.setState({timeInterval: args.timeInterval});

             if (args.autoStart === true){
               this.startTimer();
             }
          }

          private myTick = () => {
            console.log("Tick");
          }    

          private startTimer = () => {

            if (this.myType === TIMER.INTERVAL) {
              this.myTimer = setInterval(this.myTick, this.timeInterval);
              this.started = true;
            } else if (this.myType === TIMER.TIMEOUT) {
              this.myTimer = setTimeout(this.myTick, this.timeInterval);
              this.started = true;
            }

          }

         private isStarted () : bool {
           return this.started;
         }

     ...
     }
Nikola Lukic
la source