„Der falsche setTimeout“: Wie TypeScript Entwickler in den Wahnsinn treibt
\nHaben Sie jemals einen einfachen Timer in TypeScript geschrieben, und plötzlich begann der Code-Editor alles rot zu unterstreichen? Ein bekanntes Bild: Sie importieren setTimeout, schreiben const timer = setTimeout(...), und TypeScript gibt einen Fehler aus oder schlägt einen unverständlichen Typ vor. Woran liegt das? Es stellt sich heraus, dass es zwei Versionen von setTimeout gibt – eine für den Browser (window), eine für den Server (Node.js). Und TypeScript, der Perfektionist, zwingt uns zur Auswahl.
Dieses Thema ist eines der beliebtesten auf Stack Overflow. Tausende von Entwicklern stolpern über denselben Hürden. Aber wir werden nicht einfach die Antwort kopieren. Wir werden die Ursache des Problems verstehen, nachvollziehen, wie die Typisierung funktioniert, und lernen, Code zu schreiben, der weder im Browser noch in Node.js kaputtgeht.
\n\nWarum zwei Versionen von setTimeout?
\nHistorisch bedingt lebt JavaScript in zwei Welten: Client-seitig (Browser) und Server-seitig (Node.js). Im Browser ist setTimeout eine Methode des globalen Objekts window. In Node.js ist es eine Funktion aus dem globalen Modul timers. Sie tun dasselbe, aber ihre Typsignaturen unterscheiden sich.
Hier sind die wichtigsten Unterschiede:
\n- \n
- Rückgabewert: Im Browser gibt
window.setTimeouteinenumber(Timer-ID) zurück. In Node.js ein ObjektNodeJS.Timeout. \n - Kontext: Im Browser ist
thisinnerhalb des Callbackswindow(im nicht-strengen Modus). In Node.js das Timer-Objekt. \n - Typisierung: TypeScript stellt unterschiedliche Deklarationen für verschiedene Umgebungen bereit. Wenn Sie universellen Code (isomorphe Anwendung) schreiben, entsteht ein Konflikt. \n
Wie das Problem im Code aussieht
\nStellen Sie sich vor, Sie entwickeln eine Bibliothek, die sowohl auf dem Client als auch auf dem Server funktionieren soll. Sie schreiben diesen Code:
\n// utils.ts\nexport function delay(ms: number): Promise<void> {\n return new Promise(resolve => {\n const timer = setTimeout(resolve, ms);\n // Welchen Typ hat timer? number oder NodeJS.Timeout?\n });\n}\nTypeScript sieht, dass setTimeout zwei Arten haben kann, und gibt einen Fehler aus: "Type 'Timeout' is not assignable to type 'number'." Oder umgekehrt, abhängig von den Einstellungen in tsconfig.json und den eingebundenen Typen.
Zur Veranschaulichung, so sehen die Typen aus, wenn man sie durch ReturnType betrachtet:
// Im Browser (lib.dom.d.ts)\ntype BrowserTimer = ReturnType<typeof window.setTimeout>; // number\n\n// In Node.js (@types/node)\ntype NodeTimer = ReturnType<typeof setTimeout>; // NodeJS.Timeout\n\nDie richtigen Lösungen für verschiedene Szenarien
\nBetrachten wir vier Ansätze. Wählen Sie den, der zu Ihrem Projekt passt.
\n\n1. Wenn Sie nur für den Browser schreiben
\nDer einfachste Fall. Geben Sie einfach den Typ number an und verwenden Sie explizit window.setTimeout:
// browser-timer.ts\nconst timerId: number = window.setTimeout(() => {\n console.log('Eine Sekunde ist vergangen');\n}, 1000);\n\n// Zum Löschen\nwindow.clearTimeout(timerId);\nWarum funktioniert das? In der Browser-Umgebung gibt window.setTimeout immer eine Zahl zurück. TypeScript weiß das, wenn Sie lib: ["dom"] eingebunden haben.
2. Wenn Sie nur für Node.js schreiben
\nHier ist der Typ NodeJS.Timeout. Verwenden Sie ihn direkt:
// node-timer.ts\nimport { setTimeout } from 'timers'; // expliziter Import zur Klarheit\n\nconst timer: NodeJS.Timeout = setTimeout(() => {\n console.log('Timer ausgelöst');\n}, 2000);\n\nclearTimeout(timer); // clearTimeout akzeptiert NodeJS.Timeout\nWichtig: In Node.js akzeptiert clearTimeout ebenfalls No