Photo by Melanie Pongratz on Unsplash

# TypeScript and Best Practices: Modernizing the nth Function in JavaScript

Are you looking to modernize your JavaScript code? Look no further than our posts on updating code examples from books. In this third installment, we’ll show you how to modernize the `nth`

function from "Functional JavaScript." We'll walk you through updating the code, including adding types with TypeScript. Follow along and learn how to update your code with the latest best practices.

Here are the original examples:

```
function isIndexed(data) {
return _.isArray(data) || _.isString(data);
}
function nth(a, index) {
if (!_.isNumber(index)) fail("Expected a number as the index");
if (!isIndexed(a)) fail("Not supported on non-indexed type");
if (index < 0 || index > a.length - 1) fail("Index value is out of bounds");
return a[index];
}
function second(a) {
return nth(a, 1);
}
second("abc"); // b
nth([1, 2, 3], 0); // 1
nth({}, 2); // Error: Not supported on non-indexed type
```

The `nth`

function takes in an indexable value and an index. It does some guards to make sure you have actually passed in an indexable value and an index that is in bounds, and if everything is safe, it will return the value at the index. Since we already went over the `fail`

function in the previous post, I have left that code out.

Let's start with the `isIndexed`

function. This uses two utilities from underscore.js to confirm if the value is either an array or a string. There isn't an "array" type in JavaScript that you can check using the `typeof`

operator. If you call `typeof []`

, you will get "object." This is because arrays are just objects under the hood. In the past, we relied on utility functions, like `_.isArray`

to reliably check if value actually is an array and not just an object.

Luckily, we have a static method on the `Array`

object called `isArray`

that does this without needing a utility library. You can learn more about `Array.isArray`

over at MDN.. Also, in the last post, we created our own `assertIsString`

function. We can steal that logic and replace both of these checks like this:

```
function isString(value) {
return toString.call(value) === "[object String]";
}
function isIndexed(data) {
return Array.isArray(data) || isString(data);
}
```

As for the `nth`

function, the only thing we have in there that should be updated is the `_.isNumber`

function from underscore. We can adapt our `isString`

function to be an `isNumber`

function with one tweak:

```
function isNumber(value) {
return toString.call(value) === "[object Number]";
}
```

So we can update the example to look like this:

```
function isNumber(value) {
return toString.call(value) === "[object Number]";
}
function isString(value) {
return toString.call(value) === "[object String]";
}
function isIndexed(data) {
return Array.isArray(data) || isString(data);
}
function nth(a, index) {
if (!isNumber(index)) fail("Expected a number as the index");
if (!isIndexed(a)) fail("Not supported on non-indexed type");
if (index < 0 || index > a.length - 1) fail("Index value is out of bounds");
return a[index];
}
function second(a) {
return nth(a, 1);
}
second("abc"); // b
nth([1, 2, 3], 0); // 1
nth({}, 2); // Error: Not supported on non-indexed type
```

Now, let's add types. Our `is`

functions are simple enough. Each one takes an unknown value and determines if it is something, so let's type them like this:

```
function isNumber(value: unknown) {
return toString.call(value) === "[object Number]";
}
function isString(value: unknown) {
return toString.call(value) === "[object String]";
}
function isIndexed(data: unknown) {
return Array.isArray(data) || isString(data);
}
```

Now let's add the types to our `nth`

function. In the previous post, I typed the function as unknown, partially because I wanted to keep the spirit of how the function was written but partially so we could learn more about assertion functions. In a real TypeScript code base, we would write the types much more explicitly.

Our `nth`

function takes two arguments: `a`

and `index`

. `index`

is simple enough to type because it needs to be of type `number`

. `a`

could either be a string or an array. So first, let's create a type called `Indexed`

:

```
type Indexed = Array<unknown> | string;
```

This essentially says that `Indexed`

could be an array of unknown values or a string. Now we could do something like this:

```
function nth(a: Indexed, index: number) {}
```

But this will force the value of `a`

to be an array of unknown values if we pass in an array. Let TypeScript infer what type of array we are passing in would be much nicer. Luckily we can do that using generics. Let's look at the updated types:

```
function nth<T extends Indexed>(a: T, index: number): T[number] {}
```

Let's break that down. First, we are saying that `nth`

takes a generic value of `T`

that must extend the `Indexed`

type we declared. Then we say that `a`

is of type `T`

and that we are returning `T[number]`

or the value at the index of `T`

.

By having `T`

extend `Indexed`

, we are now letting Typescript infer the array's values to know the return type accurately. For example, if we call `nth([1,2,3], 0)`

, Typescript will know that the return type is of a type number.

The `second`

function below can be typed the same way, except it wont need an explicit return type since it can infer that from the `nth`

function we are calling. So now let's update the entire example using TypesScript:

```
function isNumber(value: unknown) {
return toString.call(value) === "[object Number]";
}
function isString(value: unknown) {
return toString.call(value) === "[object String]";
}
function isIndexed(data: unknown) {
return Array.isArray(data) || isString(data);
}
type Indexed = Array<unknown> | string;
function nth<T extends Indexed>(a: T, index: number): T[number] {
if (!isNumber(index)) fail("Expected a number as the index");
if (!isIndexed(a)) fail("Not supported on non-indexed type");
if (index < 0 || index > a.length - 1) fail("Index value is out of bounds");
return a[index];
}
function second<T extends Indexed>(a: T) {
return nth(a, 1);
}
second("abc"); // b
nth([1, 2, 3], 0); // 1
nth({}, 2); // TypeError
```

There you have it, a fully modernized and typed example from the classic "Functional JavaScript." I highly recommend checking it out, and I'll see you next time.