186
votes

I am working on upgrading some old TypeScript code to use the latest compiler version, and I'm having trouble with a call to setTimeout. The code expects to call the browser's setTimeout function which returns a number:

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

However, the compiler is resolving this to the node implementation instead, which returns a NodeJS.Timer:

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

This code does not run in node, but the node typings are getting pulled in as a dependency to something else (not sure what).

How can I instruct the compiler to pick the version of setTimeout that I want?

Here is the code in question:

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

This produces the compiler error:

TS2322: Type 'Timer' is not assignable to type 'number'.
7
Do you have a types:["node"] in your tsconfig.json? See stackoverflow.com/questions/42940954/…derkoe
@koe No, i don't have the types:["node"] option in the tsconfig file. But the node types are getting pulled in as an npm dependency to something else.Kevin Tighe
You could also explicitly define "types" in tsconfig.json - when you omit "node" it isn't used in compilation. e.g. "types": ["jQuery"]derkoe
It is surprising that the answer of @koe (use "types" option) doesn't have any votes, being the only true correct answer.Egor Nepomnyaschih
@KevinTighe's types doesn't include node but setTimeout still gets its Node type rather than its browser type. types defaults to all the types in node_modules/@types, as explained in typescriptlang.org/tsconfig#types, but even if you do specify types and don't include "node", why does setTimeout still get its Node type and how can you get the browser type? @Axke's solution is a bit of a hack, basically saying it returns what it returns. TypeScript may still be finding the wrong type, but at least it will be consistently wrong.Denis Howe

7 Answers

175
votes

2021 update

Akxe's answer suggests ReturnType<Type> technique introduced in Typescript 2.3:

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

It is nice and seems to be preferred over explicit casting. But the result type of "n" in this case is "NodeJS.Timeout", and it is possible to use it as follows:

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

The only problem with ReturnType/NodeJS.Timeout approach is that numeric operations in browser-specific environment still require casting:

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

Original answer

A workaround that does not affect variable declaration:

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

Also, in browser-specific environment it is possible to use window object with no casting:

let n: number;
n = window.setTimeout(function () { /* snip */  }, 500);
203
votes
let timer: ReturnType<typeof setTimeout> = setTimeout(() => { ... });

clearTimeout(timer);

By using ReturnType<fn> you are getting independence from platform. You won't be forced to use any nor window.setTimeout which will break if you run the code no nodeJS server (eg. server-side rendered page).


Good news, this is also compatible with Deno!

24
votes

I guess it depends on where you will be running your code.

If your runtime target is server side Node JS, use:

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

If your runtime target is a browser, use:

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

This will likely work with older versions, but with TypeScript version ^3.5.3 and Node.js version ^10.15.3, you should be able to import the Node-specific functions from the Timers module, i.e.:

import { setTimeout } from 'timers';

That will return an instance of Timeout of type NodeJS.Timeout that you can pass to clearTimeout:

import { clearTimeout, setTimeout } from 'timers';

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

clearTimeout(timeout);
1
votes

If you're targeting setInterval of window. Then you can also write it as

let timerId: number = setInterval((()=>{
    this.populateGrid(true)
  }) as TimerHandler, 5*1000)
}
0
votes

I faced the same problem and the workaround our team decided to use, was just to use "any" for the timer type. E.g.:

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

It will work with both implementations of setTimeout/setInterval/clearTimeout/clearInterval methods.

0
votes

I solved this problem by setting

tsconfig.json:
{
  "compilerOptions": {
    "skipLibCheck": true,
  }
}

And create .d.ts

*.d.ts:
declare namespace NodeJS {
    type Timeout = number;
    type Timer = number;
}

typescript version 4.2.3