Generics in TypeScript in JavaScript

Describe and check generic functions with JSDoc | Written by Nathan

Following up on my previous article about TypeScript in JavaScript, I’d like to spend some time talking about using generics inside the JSDoc comments to get some type checking for more complex JavaScript functions. JSDoc calls these “template functions” and we will use @template to declare our generic type.

Just in case you are not familiar with generic functions in TypeScript, they are a way to make a function take more than one type. This is what the word “generic” is meant to convey: it’s generic in that it applies to many things similarly or polymorphic. Also the JSDoc term “template” does make sense if you think about the function being a template which can be applied to many different types. If that doesn’t make sense or you are new to generic functions, then spend some time reading the TypeScript docs and maybe even watch this YouTube video about generics in TypeScript.

My example function to demonstrate generics is one that quickly inspects what is inside an array, then return that array. This is so we can slip this function in the middle of some code without changing what that code does or returns:

/**
 * @template T
 * @param {Array<T>} arr
 * @param {string} prefix
 * @returns {Array<T>}
 */
function inspect(arr, prefix = '') {
  if (prefix.length > 0) {
    console.debug(prefix)
  }

  arr.forEach((item, index) => {
    console.debug(index)
    console.table(item)
  })

  return arr
}

You can see this function accepts any kind of array. The type of the arr could be read as “an array of T” meaning an array of any type.

Just so you know, it is possible to “constrain” T so that it doesn’t represent literally any type, but just some types. So maybe only arrays of strings or numbers, for example.

Here is an example using the inspect function inside a fictitious “chat reducer” for a redux-style application:

export function chatReducer(state, action) {
  switch (action.type) {
    case SEND_MESSAGE:
      return {
        messages: inspect([...state.messages, action.payload])
      }
    default:
      return state
  }
}

Hopefully you see above where I’m able to slip the inspect function into the return value of the chatReducer so I will get an output of the full list of messages anytime a new message is sent.

One could go through and provide JSDoc comments for this chatReducer function, all the possible actions, and the payloads to help add type checking and possibly catch bugs inside a javascript application like this. It’s worth considering if this could help provide more confidence to your JavaScript team or add rigor to a project that maybe feels a bit unwieldy or hard to refactor.

Also, if you are unfamiliar with console.table, then definitely try it out, it’s super handy.