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.
