TypeScript: use la versión correcta de setTimeout (nodo vs ventana)

117

Estoy trabajando para actualizar un código TypeScript antiguo para usar la última versión del compilador y tengo problemas con una llamada a setTimeout. El código espera llamar a la setTimeoutfunción del navegador que devuelve un número:

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

Sin embargo, el compilador está resolviendo esto en la implementación del nodo, que devuelve un NodeJS.Timer:

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

Este código no se ejecuta en el nodo, pero los tipos de nodos se incorporan como una dependencia de otra cosa (no estoy seguro de qué).

¿Cómo puedo indicarle al compilador que elija la versión setTimeoutque quiero?

Aquí está el código en cuestión:

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

Esto produce el error del compilador:

TS2322: El tipo 'Temporizador' no se puede asignar al tipo 'número'.

Kevin Tighe
fuente
¿Tiene tipos: ["nodo"] en su tsconfig.json? Ver stackoverflow.com/questions/42940954/…
derkoe
@koe No, no tengo la opción de tipos: ["nodo"] en el archivo tsconfig. Pero los tipos de nodos se incorporan como una dependencia de npm a otra cosa.
Kevin Tighe
1
También puede definir explícitamente "tipos" en tsconfig.json; cuando omite "nodo", no se usa en la compilación. por ejemplo, "tipos": ["jQuery"]
derkoe
1
Es sorprendente que la respuesta de @koe (use la opción "tipos") no tenga ningún voto, siendo la única respuesta correcta verdadera.
Egor Nepomnyaschih
@ KevinTighe typesno incluye nodepero setTimeoutaún obtiene su tipo de nodo en lugar de su tipo de navegador. typespor defecto a todos los tipos de node_modules/@types, como se explica en typescriptlang.org/tsconfig#types , pero incluso si lo hace especificar typesy no incluye "node", ¿por qué setTimeoutaún así obtener su tipo de nodo y cómo se puede obtener el tipo de navegador? La solución de @ Axke es un truco, básicamente dice que devuelve lo que devuelve. Es posible que TypeScript siga encontrando el tipo incorrecto, pero al menos siempre será incorrecto.
Denis Howe

Respuestas:

88

Otra solución alternativa que no afecta la declaración de variables:

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

Además, debería ser posible utilizar el windowobjeto explícitamente sin any:

let n: number;
n = window.setTimeout(function () { /* snip */  }, 500);
dhilt
fuente
25
Creo que el otro ( window.setTimeout) debería ser la respuesta correcta para esta pregunta, ya que es la solución más clara.
amik
5
Si está utilizando el anytipo, realmente no está dando una respuesta de TypeScript.
S ..
del mismo modo, el numbertipo dará lugar a errores de pelusa específicos de TypeScript, ya que la setTimeoutfunción requiere más que eso.
S ..
2
window.setTimeoutpuede causar problemas con los marcos de prueba unitarios (node.js). La mejor solución es usar let n: NodeJS.Timeouty n = setTimeout.
cchamberlain
113
let timer: ReturnType<typeof setTimeout> = setTimeout(() => { ... });

clearTimeout(timer);

Al usarlo ReturnType<fn>, obtiene independencia de la plataforma. No se verá obligado a usar anyni window.setTimeoutque se romperá si ejecuta el código sin servidor nodeJS (por ejemplo, página renderizada del lado del servidor).


Buenas noticias, ¡esto también es compatible con Deno!

Akxe
fuente
10
Mi entendimiento es que esta es la respuesta correcta y debe ser la aceptada, ya que proporciona la definición del tipo adecuado para cada plataforma de apoyo setTimeout/ clearTimeouty no utiliza any.
afenster
12
Esta es la solución si está escribiendo una biblioteca que se ejecuta tanto en NodeJS como en el navegador.
yqlim
El tipo de retorno es NodeJS.Timeoutsi se usa setTimeoutdirectamente y numbersi se usa window.setTimeout. No debería necesitar usarlo ReturnType.
cchamberlain
@cchamberlain Lo necesita mientras ejecuta la setTimeoutfunción y espera que su resultado se almacene en la variable. Pruébelo usted mismo en TS playground.
Akxe
Para el OP y para mí, TypeScript tiene el tipo de setTimeouterror (por razones que nadie puede explicar) pero al menos esta solución debería enmascarar eso de una manera un poco mejor que simplemente usar any.
Denis Howe
16

Supongo que depende de dónde ejecutará su código.

Si su destino de tiempo de ejecución es el nodo JS del lado del servidor, use:

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

Si su objetivo de tiempo de ejecución es un navegador, use:

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

Es probable que esto funcione con versiones anteriores, pero con la versión TypeScript ^3.5.3y la versión Node.js ^10.15.3, debería poder importar las funciones específicas de Node desde el módulo Timers , es decir:

import { setTimeout } from 'timers';

Eso devolverá una instancia de Timeout de tipo NodeJS.Timeoutque puede pasar a clearTimeout:

import { clearTimeout, setTimeout } from 'timers';

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

clearTimeout(timeout);
Nick Bernard
fuente
1
Del mismo modo, si desea la versión del navegador de setTimeout, algo como const { setTimeout } = windowaclarará esos errores.
Jack Steam
3

Enfrenté el mismo problema y la solución alternativa que nuestro equipo decidió usar fue simplemente usar "cualquiera" para el tipo de temporizador. P.ej:

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

Funcionará con ambas implementaciones de los métodos setTimeout / setInterval / clearTimeout / clearInterval.

Mark Dolbyrev
fuente
2
Sí, eso funciona. También me di cuenta de que puedo especificar el método directamente en el objeto de la ventana: window.setTimeout (...). No estoy seguro de si esa es la mejor manera de hacerlo, pero me quedaré con ella por ahora.
Kevin Tighe
1
Puede importar el espacio de nombres NodeJS correctamente en mecanografiado, consulte esta respuesta .
hlovdal
Para responder realmente a la pregunta ("cómo puedo indicarle al compilador que elija la versión que quiero"), puede usar window.setTimeout () en su lugar, como @dhilt respondió a continuación.
Anson VanDoren
window.setTimeoutpuede no funcionar con marcos de prueba unitarios. Hay un tipo que se puede utilizar aquí .. Su NodeJS.Timeout. Puede pensar que no está en un entorno de nodo, pero tengo noticias para usted: Webpack / TypeScript, etc.están ejecutando node.js.
cchamberlain
1
  • Si desea una solución real para mecanografiar sobre temporizadores, aquí vamos:

    El error está en el tipo de retorno 'número', no es Timer ni nada más.

    Esto es para la solución mecanografiada versión ~> 2.7:

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

Ahora arreglamos todo, solo declaramos así:

 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
fuente