TypeScript - use a versão correta de setTimeout (nó vs janela)

111

Estou trabalhando na atualização de algum código TypeScript antigo para usar a versão mais recente do compilador e estou tendo problemas com uma chamada para setTimeout. O código espera chamar a setTimeoutfunção do navegador que retorna um número:

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

No entanto, o compilador está resolvendo isso para a implementação do nó, que retorna um NodeJS.Timer:

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

Este código não é executado no nó, mas as tipificações do nó estão sendo puxadas como uma dependência de outra coisa (não tenho certeza do que).

Como posso instruir o compilador a escolher a versão setTimeoutque desejo?

Aqui está o código em questão:

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

Isso produz o erro do compilador:

TS2322: O tipo 'Timer' não pode ser atribuído ao tipo 'número'.

Kevin Tighe
fonte
Você tem tipos: ["node"] em seu tsconfig.json? Consulte stackoverflow.com/questions/42940954/…
koe
@koe Não, eu não tenho a opção types: ["node"] no arquivo tsconfig. Mas os tipos de nós estão sendo puxados como uma dependência npm para outra coisa.
Kevin Tighe
1
Você também pode definir explicitamente "tipos" em tsconfig.json - quando você omite "nó", ele não é usado na compilação. por exemplo, "tipos": ["jQuery"]
koe
1
É surpreendente que a resposta de @koe (usar a opção "tipos") não tenha nenhum voto, sendo a única resposta verdadeira e correta.
Egor Nepomnyaschih

Respostas:

87

Outra solução alternativa que não afeta a declaração da variável:

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

Além disso, deve ser possível usar o windowobjeto explicitamente sem any:

let n: number;
n = window.setTimeout(function () { /* snip */  }, 500);
dhilt
fonte
25
Acho que a outra ( window.setTimeout) deve ser a resposta correta para essa pergunta, pois é a solução mais clara.
amik
5
Se você estiver usando o anytipo, não está realmente dando uma resposta TypeScript.
S ..
da mesma forma, o numbertipo levará a erros de lint específicos do TypeScript, pois a setTimeoutfunção requer mais do que isso.
S ..
1
window.setTimeoutpode causar problemas com estruturas de teste de unidade (node.js). A melhor solução é usar let n: NodeJS.Timeoute n = setTimeout.
cchamberlain
102
let timer: ReturnType<typeof setTimeout> = setTimeout(() => { ... });

clearTimeout(timer);

Ao usar, ReturnType<fn>você obtém independência da plataforma. Você não será forçado a usar anynem o window.setTimeoutque irá quebrar se você executar o código no servidor nodeJS (por exemplo, página renderizada do lado do servidor).


Boas notícias, isso também é compatível com Deno!

Akxe
fonte
9
Meu entendimento é que esta é a resposta certa e deve ser aceita, uma vez que fornece a definição de tipo certa para cada plataforma que suporta setTimeout/ clearTimeoute não usa any.
depois de
11
Esta é a solução se você estiver escrevendo uma biblioteca que roda tanto no NodeJS quanto no navegador.
yqlim de
O tipo de retorno é NodeJS.Timeoutse estiver usando setTimeoutdiretamente e numberse estiver usando window.setTimeout. Não deve ser necessário usar ReturnType.
cchamberlain
@cchamberlain Você precisa disso enquanto executa a setTimeoutfunção e espera que seu resultado seja armazenado na variável. Experimente você mesmo no playground do TS.
Akxe
14

Acho que depende de onde você executará seu código.

Se o destino do tempo de execução for Nó JS do servidor, use:

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

Se o seu destino de tempo de execução for um navegador, use:

let timeout: number;
window.clearTimeout(timeout);
cwouter
fonte
4

Enfrentei o mesmo problema e a solução alternativa que nossa equipe decidiu usar foi apenas usar "qualquer" para o tipo de cronômetro. Por exemplo:

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

Ele funcionará com ambas as implementações dos métodos setTimeout / setInterval / clearTimeout / clearInterval.

Mark Dolbyrev
fonte
2
Sim, isso funciona. Também percebi que posso apenas especificar o método diretamente no objeto de janela: window.setTimeout (...). Não tenho certeza se esse é o melhor caminho a percorrer, mas vou ficar com ele por enquanto.
Kevin Tighe
1
Você pode importar o namespace NodeJS corretamente no texto digitado, veja esta resposta .
hlovdal
Para realmente responder à pergunta ("como posso instruir o compilador a escolher a versão que desejo"), você pode usar window.setTimeout (), como @dhilt respondeu abaixo.
Anson VanDoren
window.setTimeoutpode não funcionar com estruturas de teste de unidade. Existe um tipo que pode ser usado aqui .. Seu NodeJS.Timeout. Você pode pensar que não está em um ambiente de nó, mas tenho novidades para você: Webpack / TypeScript etc. estão executando node.js.
cchamberlain
4

Isso provavelmente funcionará com versões mais antigas, mas com a versão TypeScript ^3.5.3e a versão Node.js ^10.15.3, você deve ser capaz de importar as funções específicas do Node do módulo Timers , ou seja:

import { setTimeout } from 'timers';

Isso retornará uma instância de Timeout do tipo NodeJS.Timeoutque você pode passar para clearTimeout:

import { clearTimeout, setTimeout } from 'timers';

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

clearTimeout(timeout);
Nick Bernard
fonte
1
Da mesma forma, se você quiser a versão do navegador setTimeout, algo como const { setTimeout } = windowlimpará esses erros.
Jack Steam
1
  • Se você quer uma solução real para datilografia sobre timers, vamos lá:

    Bug está no tipo de retorno 'número', não é Timer ou qualquer outra coisa.

    Isso é para scripts de versão ~> solução 2.7:

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

Agora que consertamos tudo, basta declarar assim:

 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
fonte