Trouble with TypeScript Timeouts: How to Fix It
- Comments:
- 1
The tl;dr is this:
You can fix the problem by replacing number
with ReturnType<typeof setTimeout>
Similarly you can replace number
with ReturnType<typeof setInterval>
if you are seeing the problem with setInterval
.
If you want to know more about it, read on.
I've run into this problem a number of times where I'm developing frontend TypeScript and after I import a package, I suddenly find throughout my project I'm receiving:
error TS2322: Type 'Timeout' is not assignable to type 'number'.
If you are working on a Node project rather than a front end project this can also manifest itself as the opposite
error TS2322: Type 'number' is not assignable to type 'Timeout'
Usually this happens because some dependency included @types/node
under dependencies
instead of devDependencies
. When that happens, Node's global types leak into your project—even in frontend environments—causing setTimeout
to return a Timeout
object instead of a number
.
The heart of the problem is that setTimeout()
and setInterval()
in Node API return a Timeout
whereas in the HTML DOM Window API, they return an integer.
The ideal solution would be for the offending package to fix the problem themselves, and for you to update to the corrected version of the package. This is a hassle and might not be possible, with the issue going ignored for months if not years - as on Facebook's Jest package [1] [2].
For example, consider the following:
let x : number = setTimeout(() => console.log("foo"), 1000);
Your immediate reaction might be to either change the definition to let x : Timeout
or eliminate the explicit type definition all together à la
let x = setTimeout(() => console.log("foo"), 1000);
Both of these options work.
The first option of setting it to Timeout
suffers from two issues though. Firstly it's fragile - presumably you're not explicitly including @types/node
so the change to the environment is a side effect you're coding around. Should the specific packages you include change, or said package correct this it will break. Second, it's ambiguous. Timeout
isn’t a global type—it's specific to Node's type system—so using it assumes a Node environment even if you're working in the browser. That can confuse collaborators or tooling down the line.
The second option of implicit typing will work in a lot of cases just fine. There are many cases where you'd want for instance to define the variable in a higher scope than you are setting its value - as a trivial example
class Foo {
private x? : number;
start() {
this.x = setTimeout(() => console.log("foo"), 1000);
}
stop() {
clearTimeout(this.x);
}
}
The answer is frustratingly simple. We can just use the ReturnType
utility type to set our type to the return type of setTimeout. We can do this using ReturnType<typeof setTimeout>
.
Our examples from above become
let x : ReturnType<typeof setTimeout> = setTimeout(() => console.log("foo"), 1000);
and
class Foo {
private x?: ReturnType<typeof setTimeout>;
start() {
this.x = setTimeout(() => console.log("foo"), 1000);
}
stop() {
clearTimeout(this.x);
}
}
Similarly, if you are using setInterval
you can utilize type ReturnType<typeof setInterval>
for similar purposes.