Space Cat, Prince Among Thieves

The Trouble with TypeScript Timeouts

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 Type 'number' is not assignable to type 'Timeout'

Usually this happens because a dependency you've included has included @types/node under dependencies when it should have included it under devDependencies. Usually, unless you are actually developing a type package, @types packages should be a dev dependency.

The ideal solution would be for the package to fix the problem and you to pull the corrected version of the package. This however might not be possible, with the issue going ignored for months if not years - as on Facebook's Jest package [1] [2].

So let us take the following example line

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 ala

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 here is a side effect you're coding around. Should the specific packages you include change, or said package correct this it will break.

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 it's 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 setTimeout> for similar purposes.


Comment by: Benjamin on

Benjamin's Gravatar Hi there! Thanks for this blogpost!

I'm currently trying to cross-compile my rx_webstreams library (https://github.com/codemonument/deno_rx_webstreams) from deno to npm with the dnt tool (https://github.com/denoland/dnt)

And I was very confused about this error there.

This saved me a lot of trial and error, thank you!

Email address will never be publicly visible.

See my Tweet about comment formatting.