Vyhledávání na webu

TypeScript - použijte správnou verzi setTimeout (node vs window)


Pracuji na aktualizaci některého starého TypeScript kódu, aby používal nejnovější verzi kompilátoru, a mám problém s voláním setTimeout. Kód očekává volání funkce setTimeout z prohlížeče, která vrací číslo:

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

Komplikátor to však místo toho řeší na implementaci uzlu, která vrací NodeJS.Timer:

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

Tento kód neběží v Node, ale typy pro Node jsou zahrnuty jako závislost na něco jiného (nejsem si jistý, na co).

Jak mohu instruovat překladač, aby vybral verzi setTimeout, kterou chci?

Zde je kód, o kterém je řeč:

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

TS2322: Typ 'Timer' nelze přiřadit k typu 'number'.

To způsobuje chybu kompilátoru:

Odpověď č. 1

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

clearTimeout(timer);

Použitím ReturnType<fn> získáváte nezávislost na platformě. Nebudete nuceni používat ani any, ani window.setTimeout, což se skutečně pokazí, pokud spustíte kód na serveru NodeJS (např. stránce renderované na straně serveru).


Dobrá zpráva, toto je také kompatibilní s Denem!

Odpověď č. 2

Aktualizace 2021

Odpověď Akxe naznačuje techniku ReturnType<Type> představenou v Typescriptu 2.3:

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

Je to pěkné a zdá se, že je to preferováno před explicitním přetypováním. Ale výsledný typ „n“ je v tomto případě „NodeJS.Timeout“ a je možné jej použít následujícím způsobem:

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

Jediným problémem přístupu ReturnType/NodeJS.Timeout je, že číselné operace v prostředí specifickém pro prohlížeč stále vyžadují přetypování:

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

Původní odpověď

Obejití, které neovlivňuje deklaraci proměnné:

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

Také je v prostředí specifickém pro prohlížeč možné používat objekt window bez přetypování:

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

Odpověď č. 3

Myslím, že to záleží na tom, kde budete spouštět svůj kód.

Pokud je vaším cílovým prostředím Node.js na straně serveru, použijte:

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

Pokud je vaším cílovým prostředím pro běh prohlížeč, použijte:

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

Odpověď č. 4

To pro mě funguje dokonale.

type Timer = ReturnType<typeof setTimeout>

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

Odpověď č. 5

To pravděpodobně bude fungovat se staršími verzemi, ale s verzí TypeScriptu ^3.5.3 a verzí Node.js ^10.15.3 byste měli být schopni importovat funkce specifické pro Node z modulu Timers, tj.:

import { setTimeout } from 'timers';

To vrátí instanci Timeout typu NodeJS.Timeout, kterou můžete předat clearTimeout:

import { clearTimeout, setTimeout } from 'timers';

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

clearTimeout(timeout);

Odpověď č. 6

Chtěl jsem také zmínit, že specifikace pro NodeJS.Timeout zahrnuje [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;
}

A pro kompatibilitu ostatní timeout API v Node fungují s obyčejnými celočíselnými ID naprosto dobře, nemusí přijímat objekt. Objekty jsou používány na „serverové“ straně, aby umožnily jemnější kontrolu nad udržováním procesu v chodu a správou paměti. Například:

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

To znamená, že můžete použít primitivní typ pro výsledek setTimeout a setInterval:

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

function clear() {
  clearTimeout(timeoutId);
}

Nekoliduje s žádným z API a zároveň vás nezavádí do problémů s typy později, pokud byste se museli spolehnout na to, že jde o primitivní hodnotu pro nějakou jinou smlouvu API.

Související články