All Articles

Make Your Functions More Reusable With Generics

Matt Pocock
Matt PocockMatt is a well-regarded TypeScript expert known for his ability to demystify complex TypeScript concepts.

Here's a challenge for you. Our retry function below doesn't infer the type of the resolved promise. Can you fix it?

Why Is The Error Happening?

The error above is happening because we're using any as the return type of the promise in the retry function:

async function retry(
  fn: () => Promise<any>,
  retries: number = 5
): Promise<any> {
  // ...
}

This means that when we call the retry function, TypeScript can't infer the type of the resolved promise. It's just any.

This is bad, because any disables type checking on anything it's applied to. This means that just by using our retry function, we're losing type safety on whatever we pass into it.

This is a common problem when you're working with reusable functions in TypeScript - it's tempting to slap an any on there and move on. But with just a bit of extra work, we can make our functions much more flexible and type-safe.

Solution: Use A Type Parameter

Instead of using any, we can use a type parameter to make the retry function more flexible:

ts
async function retry<T>(
fn: () => Promise<T>,
retries: number = 5
): Promise<T> {
try {
return await fn();
} catch (err) {
if (retries > 0) {
console.log("Retrying...");
return await retry(fn, retries - 1);
}
throw err;
}
}

We've added a type parameter T to the retry function. We're then referencing it in the fn parameter as the thing we expect to get back from our promise. Finally, we use it as the return type of the retry function.

This means that when we call the retry function, TypeScript can infer the type of the resolved promise. It's no longer any - it's the type we return from our promise.

ts
const getString = () => Promise.resolve("hello");
 
retry(getString).then((str) => {
// str is string, not any!
console.log(str);
});

We can name T anything we like: TData or TResponse are common choices. I like using T at the start to represent 'type parameter'.

Our retry function is now a generic function - it captures type information from the runtime values passed in. This means it's a lot more reusable and safe to use.

Generics

If you're interested in learning more about generics, my course Total TypeScript has an entire module covering them in-depth.

Or, you could check out the other articles I've written on generics on this site.

Matt's signature

Share this article with your friends

`any` Considered Harmful, Except For These Cases

Discover when it's appropriate to use TypeScript's any type despite its risks. Learn about legitimate cases where any is necessary.

Matt Pocock
Matt Pocock

No, TypeScript Types Don't Exist At Runtime

Learn why TypeScript's types don't exist at runtime. Discover how TypeScript compiles down to JavaScript and how it differs from other strongly-typed languages.

Matt Pocock
Matt Pocock

Deriving vs Decoupling: When NOT To Be A TypeScript Wizard

In this book teaser, we discuss deriving vs decoupling your types: when building relationships between your types or segregating them makes sense.

Matt Pocock
Matt Pocock

NoInfer: TypeScript 5.4's New Utility Type

Learn how TypeScript's new utility type, NoInfer, can improve inference behavior by controlling where types are inferred in generic functions.

Matt Pocock
Matt Pocock