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”.


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);

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!


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);

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);

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.


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;
         }

     ...
     }

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)
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.