B.James
B.James

Reputation: 196

Typescript type check on callback

I have recently started learning Typescript and ran into this problem that I do not understand conceptually. Is it wrong to define parameter type for this function?

setTimeout((a: string) => {
    console.log(a); // ==> Prints 5 as a number
}, 1, 5);

Why isn't there type checking performed when calling callback?

How would I go about making typescript throw an error?

What are my solutions in general, when encountering something like this?

Upvotes: 2

Views: 533

Answers (1)

jcalz
jcalz

Reputation: 329258

This is a known bug in TypeScript, see microsoft/TypeScript#32186. The typings for setTimeout() look like

setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;

which accepts any argument list whatsoever, without caring about the types. And TimerHandler is defined as just

type TimerHandler = string | Function

which uses the unsafe and untyped Function type. So setTimeout() is unfortunately very loosely typed in TypeScript. Often in situations like this you could use declaration merging to add better call signatures, but in this case nothing will stop the compiler from falling back to the unsafe call signature.

This is just a limitation in TS, it seems. The issue is listed as being in the "Backlog", so it's not slated to be addressed anytime soon. You could go to microsoft/TypeScript#32186 and give it a 👍 and show support for it being fixed, but it's not likely to change much.

For now there are only workarounds.


You could decide to use your own local copy of the lib.dom.ts library, where you modify the definition of safeTimeout(), but that would be very annoying to maintain.

A workaround you could use is to wrap setTimeout in your own function that is more strongly typed, and then use only that Perhaps something like this:

const safeSetTimeout: <A extends any[]>(
    handler: (...args: A) => void, timeout?: number, ...args: A
) => number = setTimeout;

That uses generics to strongly type the args expected by the handler callback to be same as that passed as the args passed to setTimeout() itself. Let's see it work:

safeSetTimeout((a: string) => {
    console.log(a);
}, 1, 5); // <-- error!  number is not assignable to string
//    ~

Great, we get the expected compiler error on 5, saying that it should be a string and not a number. That's about as good as it gets for now, unfortunately.

Playground link to code

Upvotes: 3

Related Questions