TypeScript - bruk korrekt versjon av setTimeout (node vs window)


Jeg jobber med å oppgradere noe gammel TypeScript-kode for å bruke den nyeste kompilatorversjonen, og jeg har problemer med et kall til setTimeout. Koden forventer å kalle nettleserens setTimeout-funksjon som returnerer et tall:

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

Imidlertid løser kompilatoren dette til node-implementeringen i stedet, som returnerer en NodeJS.Timer:

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

Denne koden kjører ikke i Node, men node-typene blir hentet inn som en avhengighet til noe annet (ikke sikker på hva).

Hvordan kan jeg instruere kompilatoren til å velge den versjonen av setTimeout som jeg ønsker?

Her er koden det gjelder:

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

TS2322: Type 'Timer' kan ikke tilordnes type 'number'.

Dette gir kompilatorfeilen:

Svar #1

let timer: ReturnType<typeof setTimeout> = setTimeout(() => { ... });

clearTimeout(timer);

Ved å bruke ReturnType<fn> får du uavhengighet fra plattform. Du vil ikke bli tvunget til å bruke verken any eller window.setTimeout, som vil bryte hvis du kjører koden på en nodeJS-server (for eksempel en server-side rendret side).


Gode nyheter, dette er også kompatibelt med Deno!

Svar #2

Oppdatering 2021

Akxes svar antyder ReturnType<Type>-teknikken introdusert i Typescript 2.3:

let n: ReturnType<typeof setTimeout>;
n = setTimeout(cb, 500);

Det er fint og ser ut til å være foretrukket fremfor eksplisitt casting. Men resultattypen av "n" i dette tilfellet er "NodeJS.Timeout", og det er mulig å bruke det som følger:

let n: NodeJS.Timeout;
n = setTimeout(cb, 500);

Det eneste problemet med ReturnType/NodeJS.Timeout-tilnærmingen er at numeriske operasjoner i nettleserspesifikke miljøer fortsatt krever typekonvertering:

if ((n as unknown as number) % 2 === 0) {
  clearTimeout(n);
}

Opprinnelig svar

En løsning som ikke påvirker variabeldeklarasjonen:

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

Også, i et nettleserspesifikt miljø er det mulig å bruke window-objektet uten å måtte kaste:

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

Svar #3

Jeg antar det avhenger av hvor du vil kjøre koden din.

Hvis kjøretidsmålet ditt er server-side Node JS, bruk:

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

Hvis kjøretidsmålet ditt er en nettleser, bruk:

let timeout: number;
window.clearTimeout(timeout);

Svar #4

Dette fungerer helt perfekt for meg.

type Timer = ReturnType<typeof setTimeout>

const timer: Timer = setTimeout(() => {}, 1000)

Svar #5

Dette vil sannsynligvis fungere med eldre versjoner, men med TypeScript-versjon ^3.5.3 og Node.js-versjon ^10.15.3 burde du kunne importere Node-spesifikke funksjoner fra Timers-modulen, altså:

import { setTimeout } from 'timers';

Det vil returnere en instans av Timeout av typen NodeJS.Timeout som du kan sende til clearTimeout:

import { clearTimeout, setTimeout } from 'timers';

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

clearTimeout(timeout);

Svar #6

Ville også nevne at spesifikasjonen for NodeJS.Timeout inkluderer [Symbol.toPrimitive](): number:

interface Timeout extends Timer {
    /**
     * If true, the `Timeout` object will keep the Node.js event loop active.
     * @since v11.0.0
     */
    hasRef(): boolean;
    /**
     * Sets the timer's start time to the current time, and reschedules the timer to
     * call its callback at the previously specified duration adjusted to the current
     * time. This is useful for refreshing a timer without allocating a new
     * JavaScript object.
     *
     * Using this on a timer that has already called its callback will reactivate the
     * timer.
     * @since v10.2.0
     * @return a reference to `timeout`
     */
    refresh(): this;
    [Symbol.toPrimitive](): number;
}

Og for kompatibilitet fungerer de andre timeout-APIene i Node helt fint med vanlige heltalls-IDer, de trenger ikke å akseptere objektet. Objektene brukes «server»-side for å tillate litt finere kontroll over å holde prosessen i live og søppelinnsamling. For eksempel:

function clearTimeout(timeoutId: NodeJS.Timeout | string | number | undefined): void;

Dette betyr at du kan bruke en primitiv typekonvertering på resultatet av setTimeout og setInterval:

let timeoutId: number | undefined;
timeoutId = Number(setTimeout(callback, ms));

function clear() {
  clearTimeout(timeoutId);
}

Konflikter ikke med noen av API-ene, samtidig som det ikke fører til typeproblemer senere hvis du skulle trenge å stole på at det er en primitiv verdi for en annen API-kontrakt.

Relaterte artikler