Practical advanced TypeScript - Writing a function wrapper
Tags: typescript
Published: 2020-08-08 16:54:06

# Typing a wrapper function for fetch

A while back, I needed a function that would behave like `fetch` does, but so it would always have specific headers set to make it easier for the consumer. I also wanted to have the possibility to add more defaults as I go and to create variations of this function as some of our API endpoints were returning JSON and some plain text. 

For this, I decided to create a function, that would accept the `fetch` function as an argument and would return a new instance of said function with given defaults pre-applied.

```ts
const fetchWithJSONHeaders = applyDefaults(fetch, {
  headers: {
    "Content-Type": "application/json"
  }
});

const fetchWithTextHeaders = applyDefaults(fetch, {
  headers: {
    "Content-Type": "application/text"
  }
});

// Fetch JSON content
const response = await fetchWithJSONHeaders("/users", {
  method: "GET" 
});
```

## Function type as a parameter type

So let's start from figuring out how we could use `window.fetch`'s signature as a parameter type of the function. 

```ts 
function applyDefaults(fetchFn: /* ??? */, ...)
```

Nothing stops you from just defining the type manually:
```ts
function applyDefaults(fetchFn: (url: string, options: object) => Promise<any>, ...)
```
However there's a downside – you now have a new type to keep up to date with changes in `fetch`'s signature. It's can also be a bit difficult to make it exactly how `fetch`'s signature is.


Luckily for us, TypeScript comes with a built-in `typeof` operator that you can use precisely for this.

```ts 
function applyDefaults(fetchFn: typeof fetch, ...)
```

So now we have our first parameter solved 🏆 Now your function only accepts functions that have the precisely same signature as `fetch`.

## Reading function parameter types

Onto the second parameter. You want the parameter to accept all the options `fetch` would accept. Let's take a look at how `window.fetch` is typed:

```ts
function fetch(input: RequestInfo, init?: RequestInit | undefined): Promise<Response>
```

How this we see, that there exists a type `RequestInit` that we could use for our function as well

```ts 
function applyDefaults(fetchFn: typeof fetch, defaults: RequestInit)
```

Using the underlying types directly like this is in most cases the best way to go. 

In rare cases, the underlying types aren't exposed from the library. What shall we do then? Maybe we could also use the `typeof` operator here too and combine it with a TypeScript's built-in type `Parameters`. `Parameters` becomes useful whenever you want to extract the type of parameters from a function type:

```ts
type FetchParams = Parameters<typeof fetch>

// Is now equal to
type FetchParams = [RequestInfo, (RequestInit | undefined)?]
```

So the way you would use this in your function definition would be `Parameters<typeof fetch>[1]`. `[1]` here is just a way for you to pick the second type from the tuple type you got from `Parameters`.

So here it is in all of its beauty:

```ts
function applyDefaults(fetchFn: typeof fetch, defaults: Parameters<typeof fetch>[1])
```

## Turning optional required

Looking good 😎 There's just one issue tho – the `defaults` parameter is now optional, as you're picking it directly from `fetch`. With fetch, you do not always need to define options for the request, but in our case, it doesn't make sense to have it as an optional parameter.

So here's how our type definition looks like at the moment:

```ts
function applyDefaults(fetchFn: typeof fetch, defaults: RequestInit | undefined)
```

How can we turn an optional parameter into a required one? The built-in `Required` type is there exactly for this.

```ts
function applyDefaults(fetchFn: typeof fetch, defaults: Required<Parameters<typeof fetch>[1]>)
```

And your function definition is done! 


You can see the full function definition here:
https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABAQwA6oDYE8AiBTYZEDKAZwApg8oIALAMTAC5EotU85hEqbaAaRABMCREqRYAFZACdkAW2p4ZpADxsOXHtToA+ANoBGALoBKRAG8AUIkQzqIGUnIA6N7IDmExNLmKoymoanNy8euYAvLqINrbafOSepPoADMaC5BZuLiKExGSC2UlGxgC+pqYA3FalVlZAA