TypeScript - użyj poprawnej wersji setTimeout (węzeł vs okno)

128

Pracuję nad uaktualnieniem starego kodu TypeScript do najnowszej wersji kompilatora i mam problem z wywołaniem do setTimeout. Kod oczekuje wywołania funkcji przeglądarki, setTimeoutktóra zwraca liczbę:

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

Jednak kompilator zamiast tego rozwiązuje to na implementację węzła, która zwraca NodeJS.Timer:

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

Ten kod nie działa w węźle, ale typy węzłów są pobierane jako zależność od czegoś innego (nie wiem co).

Jak mogę poinstruować kompilator, aby wybrał wersję setTimeout, której chcę?

Oto kod, o którym mowa:

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

Powoduje to błąd kompilatora:

TS2322: Typu „Timer” nie można przypisać do typu „numer”.

Kevin Tighe
źródło
Czy masz typy: ["węzeł"] w pliku tsconfig.json? Zobacz stackoverflow.com/questions/42940954/…
derkoe
@koe Nie, nie mam opcji types: ["node"] w pliku tsconfig. Ale typy węzłów są pobierane jako zależność npm od czegoś innego.
Kevin Tighe
1
Możesz również jawnie zdefiniować "typy" w tsconfig.json - gdy pominiesz "węzeł", nie będzie on używany w kompilacji. np. „types”: [„jQuery”]
derkoe
1
Zaskakujące jest to, że odpowiedź @koe (użyj opcji "typy") nie ma głosów, będąc jedyną prawdziwą poprawną odpowiedzią.
Egor Nepomnyaschih
@ KevinTighe typesnie obejmuje, nodeale setTimeoutnadal pobiera typ węzła, a nie typ przeglądarki. typesdomyślnie do wszystkich typów w node_modules/@types, jak wyjaśniono w typescriptlang.org/tsconfig#types , ale nawet jeśli zrobić określić typesi nie obejmują "node", dlaczego setTimeoutwciąż jej typ węzła i jak można uzyskać typ przeglądarki? Rozwiązanie @ Axke to trochę hack, mówiąc po prostu, że zwraca to, co zwraca. TypeScript może nadal znajdować niewłaściwy typ, ale przynajmniej będzie konsekwentnie błędny.
Denis Howe

Odpowiedzi:

98

Inne obejście, które nie ma wpływu na deklarację zmiennej:

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

Powinno być również możliwe windowjawne użycie obiektu bez any:

let n: number;
n = window.setTimeout(function () { /* snip */  }, 500);
dhilt
źródło
26
Myślę, że druga ( window.setTimeout) powinna być poprawną odpowiedzią na to pytanie, ponieważ jest to najwyraźniejsze rozwiązanie.
amik
5
Jeśli używasz anytypu, tak naprawdę nie dajesz odpowiedzi TypeScript.
S ..
podobnie, numbertyp prowadzi do błędów lint specyficznych dla języka TypeScript, ponieważ setTimeoutfunkcja wymaga czegoś więcej.
S ..
2
window.setTimeoutmoże powodować problemy z platformami testów jednostkowych (node.js). Najlepszym rozwiązaniem jest użycie let n: NodeJS.Timeouti n = setTimeout.
cchamberlain
127
let timer: ReturnType<typeof setTimeout> = setTimeout(() => { ... });

clearTimeout(timer);

Korzystając z ReturnType<fn>niej uzyskujesz niezależność od platformy. Nie będzie zmuszony do korzystania anyani window.setTimeoutktóry będzie złamać jeśli uruchomić kod żaden serwer nodeJS (np. Server-side wygenerowana strona).


Dobra wiadomość, to jest również kompatybilne z Deno!

Akxe
źródło
10
Rozumiem, że jest to właściwa odpowiedź i powinna być akceptowana, ponieważ zapewnia właściwą definicję typu dla każdej platformy obsługującej setTimeout/ clearTimeouti której nie używa any.
afenster
12
Jest to rozwiązanie, jeśli piszesz bibliotekę, która działa zarówno w NodeJS, jak iw przeglądarce.
yqlim
1
Zwracany typ to, NodeJS.Timeoutjeśli używasz setTimeoutbezpośrednio i numberjeśli używasz window.setTimeout. Nie powinno używać ReturnType.
cchamberlain
@cchamberlain Potrzebujesz go, gdy uruchamiasz setTimeoutfunkcję i oczekujesz, że jej wynik zostanie zapisany w zmiennej. Spróbuj sam w TS zabaw.
Akxe
Dla mnie i dla OP, TypeScript pobiera typ setTimeoutbłędu (z powodów, których nikt nie może wyjaśnić), ale przynajmniej to rozwiązanie powinno maskować to w nieco lepszy sposób niż samo użycie any.
Denis Howe
15

Myślę, że zależy to od tego, gdzie będziesz uruchamiać swój kod.

Jeśli celem środowiska wykonawczego jest Node JS po stronie serwera, użyj:

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

Jeśli celem środowiska wykonawczego jest przeglądarka, użyj:

let timeout: number;
window.clearTimeout(timeout);
cwouter
źródło
6

Prawdopodobnie zadziała to ze starszymi wersjami, ale z wersją TypeScript ^3.5.3i wersją Node.js ^10.15.3powinieneś być w stanie zaimportować funkcje specyficzne dla węzła z modułu Timers , tj .:

import { setTimeout } from 'timers';

Że zwróci instancję Timeout typu NodeJS.Timeout, które można przekazać do clearTimeout:

import { clearTimeout, setTimeout } from 'timers';

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

clearTimeout(timeout);
Nick Bernard
źródło
1
Podobnie, jeśli chcesz mieć wersję przeglądarki setTimeout, coś takiego jak const { setTimeout } = windowusunie te błędy.
Jack Steam
2

Zmierzyłem się z tym samym problemem i rozwiązaniem, które nasz zespół zdecydował się zastosować, było po prostu użycie „any” dla typu timera. Na przykład:

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

Będzie działać z obiema implementacjami metod setTimeout / setInterval / clearTimeout / clearInterval.

Mark Dolbyrev
źródło
2
Tak, to działa. Zdałem sobie również sprawę, że mogę po prostu określić metodę bezpośrednio na obiekcie okna: window.setTimeout (...). Nie jestem pewien, czy to najlepszy sposób, ale na razie się go trzymam.
Kevin Tighe
1
Możesz poprawnie zaimportować przestrzeń nazw NodeJS w maszynie, zobacz tę odpowiedź .
hlovdal
Aby właściwie odpowiedzieć na pytanie („jak mogę poinstruować kompilator, aby wybrał wersję, którą chcę”), możesz zamiast tego użyć window.setTimeout (), jak odpowiedział @dhilt poniżej.
Anson VanDoren
window.setTimeoutmoże nie działać z platformami testów jednostkowych. Jest typ, którego można tu użyć ... Jego NodeJS.Timeout. Możesz pomyśleć, że nie jesteś w środowisku węzłów, ale mam dla Ciebie wiadomość: Webpack / TypeScript itp. Wykonują node.js.
cchamberlain
0
  • Jeśli chcesz prawdziwego rozwiązania dla maszynopisu dotyczącego timerów, zaczynamy:

    Błąd jest w zwracanym typie „liczba”, nie jest to Timer ani nic innego.

    Dotyczy to rozwiązania dla maszynopisów w wersji ~> 2.7:

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

Teraz naprawiliśmy wszystko, po prostu zadeklaruj w ten sposób:

 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
źródło
0

Jeśli celem setIntervaljest window. Następnie możesz również zapisać go jako

let timerId: number = setInterval((()=>{
    this.populateGrid(true)
  }) as TimerHandler, 5*1000)
}
सत्यमेव जयते
źródło