Use Typescript generics for a type safe `setTimeout` and `setInterval`
Tags: typescript, javascript, react, node
Published: 2022-03-20 12:46:57

---
title: Use Typescript generics for a type safe `setTimeout` and `setInterval`
published: true
description: 
tags: [typescript, javascript, react, node]
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qp2ib5gb7p69uw17sxc4.png
---

## TLDR;

Here's the code:

```typescript
type Handler = ((...args: any[]) => any) | string;

function safeSetTimeout<F extends Handler>(
    handler: F,
    timeout?: number,
    ...args: F extends string ? any[] : Parameters<F extends string ? never : F>
) {
    return setTimeout(handler, timeout, ...args);
}
```

If you understand everything in the following snippet, you don't have much to gain from this post.
But you might want check out the practical snippet at the end of this post.

Otherwise, stick around and let's produce some stricter variants of your loved `setTimeout` and `setInterval`.

## The problem

If you check the type [definitions](https://github.com/microsoft/TypeScript/blob/main/lib/lib.dom.d.ts#L17376)
for timers in Typescript, you will find this:

```typescript
type TimerHandler = string | Function;
setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bcyeeppk8ws6igqn24ht.png)

The first thing this reminds us of is that these timers can take 3+ arguments, most of us are used to passing only two. The delay/interval
and some callback.

The `TimerHandler` definition also says that it can be a string, which gets `eval()`ed and executed.

I'm pretty sure you had the lecture of evil eval elsewhere, so I will not bore you with it here. But it is still interesting from
a type point of vue as the type system has no way of deducing what an arbitrary string might do. So nothing to do in this regard.

The third argument and beyond get passed to the `handler` upon invocation. But their types and and the types that the `handler`
expects are completely unrelated, those `any` and `Function` are so loosy goosy.
You could pass cabbage to a function that expects a `number` and typescript will still be happy.

Let's change that!

## The solution

We want a way to link the type of a callback function's arguments to whatever other arguments gets passed to the caller.

For example, this `apply` higher order function takes in a `callback` from `string` to `number` and string as arguments
and gives us back the result of the application of that `callback`, which Typescript accurately infers as a `number`.

```typescript
const apply = (callback: (x: string) => number, arg: string) => callback(args);
```

But what if we want to make the `callback`'s input arbitrary, after all, all that `apply` cares about is that `arg` matches
the input of `callback`

Enter generics. We can tell Typescript, hey, see this `T` ? I will give you a `callback` that consumes it and a corresponding `arg`.

```typescript
const applyGeneric = <T>(callback: (x: T) => number, arg: T) => callback(arg);
```

And when we use it like this, we get a compilation error:

```typescript
const exclaim = (x: string, times = 1) => x + '!'.repeat(times);
// Argument of type '(x: string) => string' is not assignable to parameter of type '(x: string) => number'.
//  Type 'string' is not assignable to type 'number'.
applyGeneric(exclaim, 0);
```

Typescript is not happy as the `0` "constrains" `T` to be a `number` and `exclaim` consumes `T`s of type `string`.

What about a generic return type of `callback`? easy enough. Just add another generic parameter.

```typescript
const applyGeneric = <T, R>(callback: (x: T) => R, arg: T) => callback(arg);
// Argument of type 'number' is not assignable to parameter of type 'string'.
applyGeneric(exclaim, 0);
```

And as nice side effect, notice the more specific compile error message from the previous example.

So far so good, but what if we have more than one argument to pass to `callback` ?
We could just other generic parameters to `apply` and overloads. But it gets ugly fast.

Luckily, Typescript enables us to have the type of a functions arguments using the `Parameters` utility type,
which is generic over a function type and gives us the type of its parameters as tuple type.

A function's type is essentially its signature. In this example, `Params1` and `Params2` are equivalent to the tuple type `Params3`.

```typescript
const exclaim = (x: string, times = 1) => x + '!'.repeat(times);
type Params1 = Parameters<(x: string, times?: number) => string>;
type Params2 = Parameters<typeof exclaim>;
type Params3 = [x: string, times?: number];
```

And the return type ? we have `ReturnType<F>` for that in a similar manner.

With this mind, let's get back to `applyGeneric`:

```typescript
const applyGeneric = <F extends (...args: any[]) => any>(callback: F, ...args: Parameters<F>): ReturnType<F> => {
    return callback(...args);
};
```

We have the `extends` keyword here, it's used to place a "constraint" on `F` so that it only accepts functions.
And `F` is used to tell the compiler that the type of `callback` is the same as the thing we passed to `Parameters`.

This function is so versatile you can throw any `callback` to it with any number of arguments and it will just work.

In essence, `setTimeout` and `setInterval` are higher order functions similar to our `applyGeneric`, but we don't have to worry about
the return type as it is already known. So a simple implementation would look like this:

```typescript
const safeSetTimeout = <F extends (...args: any[]) => any>(callback: F, timeout?: number, ...args: Parameters<F>) => {
    return setTimeout(callback, timeout, ...args);
};

const safeSetInterval = <F extends (...args: any[]) => any>(callback: F, timeout?: number, ...args: Parameters<F>) => {
    return setInterval(callback, timeout, ...args);
};
```

This will work for all intents and purposes, and it will force you to not pass in a `string` for the `callback`.

But if you really want to make the signatures identical, then `any` will creep in when you use a `string` for `callback`.

So coming back full circle to the snippet at the beginning of the post, the only difference from this implementation is
the use of type conditionals to revert back to the original behavior when `callback` is a `string`

Does it all make sense now ? Do you find yourself using `arguments` beyond `timeout` to timers ?

Please let me know what you think, ask questions and suggest future topics I should cover in future posts in the comments below.

Thank you for reading, I hope you found this post helpful, don't forget to follow for more 🤗.