Generic Higher-Order Functions in TypeScript

As of TypeScript 3.1, the lib.es5.d.ts file provides a couple of predefined types that are very helpful when trying to write generic higher-order functions. In this post, I’m going to show an example of using the Parameters and ReturnType predefined types for just that purpose.

The Types

TypeScript 2.8 added the ReturnType type, and TypeScript 3.1 added the Parameters type. Here are the definitions as of TypeScript 3.1:


/**
* Obtain the parameters of a function type in a tuple
*/
type Parameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never;

/**
* Obtain the return type of a function type
*/
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;

Higher-Order Function

Here’s a higher-order function that can decorate any given function so that it logs how long the given function took to run. I don’t want to lose any type checking by decorating my functions, so the higher-order function needs to be able to preserve the types of the given function.


function logDuration<T extends (...args: any[]) => any>(func: T): (...funcArgs: Parameters<T>) => ReturnType<T> {
  const funcName = func.name;

  // Return a new function that tracks how long the original took
  return (...args: Parameters<T>): ReturnType<T> => {
    console.time(funcName);
    const results = func(...args);
    console.timeEnd(funcName);
    return results;
  };
}

Try It Out

Here’s a simple function that just adds two numbers together:


function addNumbers(a: number, b: number): number {
  return a + b;
}

addNumbers has the following type signature:


function addNumbers(a: number, b: number): number

It can be decorated using logDuration so we can monitor how long it takes to execute each time it’s called.


const addNumbersWithLogging = logDuration(addNumbers);

If you check the types on addNumbersWithLogging, you’ll see that they match the input function exactly:


const addNumbersWithLogging: (a: number, b: number) => number

And when calling the new function, you’ll now see how long it took to execute:


addNumbersWithLogging(5, 3);

> addNumbers: 0.193ms

Summary

Being able to write correctly typed, generic, higher-order functions is extremely powerful. Logging how long a function takes to execute is just one simple example, but the possible uses are endless.