In seiner CodeSOD-Kolumne erzählt Remy Porter, wie Karen, eine Entwicklerin, die TypeScript-Spezifikationstests betreute, eine seltene, aber hartnäckige Instabilität aufspürte. Die funktionalen, spezifikationsbasierten Tests schlugen etwa drei- bis viermal bei jeder tausendsten Ausführung fehl, während die Unit-Tests durchweg bestanden. Die Operationen waren zeitabhängig, und die Tests nutzten bereits großzügige Zeitfenster, weshalb die Ursache nicht auf der Hand lag.
An mehreren Stellen mussten die Tests mindestens zwei Millisekunden warten. Die ursprüngliche Hilfsfunktion war der idiomatische Einzeiler:
```typescript export const wait = (ms:number) => new Promise((complete) => setTimeout(complete, ms)); ```
Die Standard-JavaScript-Dokumentation warnt, dass `setTimeout` länger als angefordert warten kann, was Karen akzeptierte. Die Node.js-Dokumentation jedoch gibt keine Garantie, wann der Callback aufgerufen wird, sodass der Timer sogar vor Ablauf der Zeit auslösen kann.
Nach längerer Fehlersuche stellte Karen fest, dass genau diese vorzeitigen Auslösungen die Fehler verursachten. Sie ersetzte die einfache Hilfsfunktion durch diese pollende Version:
```typescript export const wait = (ms: number) { const target = performance.now() + ms; return new Promise((complete) => { const checkReady = () => { if (performance.now() > target) { complete(); } else { setTimeout(checkReady, 1); } } setTimeout(checkReady, 1); }); } ```
Diese prüft jede Millisekunde die Zeit und löst die Promise erst auf, wenn die Zielzeit tatsächlich erreicht ist. Die Änderung stabilisiert die Tests, aber der Autor merkt an, dass dieser Workaround bedauerlich ist: Er existiert nur, weil Node.js die übliche Planungsgarantie anderer Runtimes verletzt – mindestens so lange wie angefordert zu warten, wenn auch möglicherweise deutlich länger.