• 1374 words

A very simple fetch wrapper ready to use

A handy thin fetch wrapper in less than 100 lines featuring abortion (with polyfill for non-supporting envs), token setter, etc.

Chocolates in line
Photo by Isabella and Zsa Fischer / Unsplash

Hi there, hope you’re enjoying the new website and blog. I graduated one month ago and so now that the university’s gone, I hope I’ll have some more time to dedicate to my personal stuff including this blog.

What is a wrapper

We obtain many different results when “wrapping” something: there are wrappers that turn out to be libraries and wrappers that are so small that they’re just an agglomerated amount of code that simplify others’ lives. Mine is one of the second kind.
I wanted to make this clear because I’ve seen projects like this that are defined as wrappers but, despite the fact that it has no dependencies included, it has so many features and imposes so much syntax* and structures that it isn’t just a wrapper anymore, but a small lib.

*For syntax I mean functions and other named components of the lib that implements a pre-defined functionality

An API “service”

In my older post, I mentioned an API “service” that I’d have shared eventually.
Here you can find the last revision of the code (it’s just a gist) but I won’t assure you to update it since I’m preparing another repo relative to some react native resources and this service will be part of it.

I started to create my own API service at the beginning of my work and the first one I used was found on a GitHub repo that I don’t have anymore (not that it’s important since it’s completely changed). It stayed the same for a long time (and it is the same in some old projects) but also for the sake of this article I decided to mutate its syntax and enhance some functionalities.

Abortion

I wrote the last article about the cancellation and abortion of HTTP requests. I sent that article to some people that requested it on a GitHub issue and a guy fixed a little error he spot in my post (it’s about the support for XHR that is, indeed, supported in react native while I thought it wasn’t, go check yourself). In the meantime, support for the AbortController API has landed in react native 0.60 and so in this module I’ve also put up an abortion implementation and a working polyfill for environments that don’t support it (based on the last article’s code).

The challenge: defining the API

It has been a little bit challenging not implementing it, but setting the boundaries of the functionalities that this wrapper had to provide and you can observe the pinnacle of these indecisions in the exported “getToken” function which is actually a strange thing considering that you could just import such a method directly inside it (I’ve added a comment about this).

This is because I wanted to give some basic functionalities but I didn’t want to impose everything on the developer. This code is intended to be used knowingly by a developer that will eventually modify it. Consider it like a piece of code you’d see and re-use from StackOverflow.

How do we return a method to abort the call?

I needed a way to execute the call and at the same time return a method with which the user could abort it. I basically started thinking to implement a function to “prepare” a call and then effectively make the call. Something like this:

const { promise, cancel } = Api.get(url);
// and then
const data = await promise();
cancel();

But I wasn’t satisfied with it. I find it too undirected and the promise method is just useless, you can’t pass any new params to that function (nor there was the need) and all that structure was there just to allow me to return a cancel function along with the promise.
So I had another idea…

The code

For brevity I’ll not insert every piece of code, to read it you can go to the gist.

const _toQueryString = ...

// EDIT here if you prefer a storage implementation or a store subscription etc.
// you could actually also remove the getToken function and directly call it in the header below
const methods = {
  getToken: () => null
};

const _makeCancelable = promise => {
  let hasCanceled = false;

const cancelablePromise = ...

const _call = (method, route, params, setAbortMethod, auth = true) => {
  // EDIT you can always strip abortcontroller from here if you need different usages
  // since it can be tricky to handle the cancel function passed to setAbortMethod for specific usage
  // sadly this is the best I could think of
  const controller = AbortController ? new AbortController() : null;
  if (method === "GET" && params) {
    route += _toQueryString(params);
  }
  const url = "https://jsonplaceholder.typicode.com/" + route;
  const options = {
    method,
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      ...(auth && methods.getToken() && { Authorization: `Bearer ${methods.getToken()}` }) // EDIT here based on your api
    },
    ...(params && method !== "GET" && { body: JSON.stringify(params) }),
    ...(controller && { signal: controller.signal })
  };
  const currentFetch = () => {
    return fetch(url, options).then(
      res => (res.ok ? res.json() : Promise.reject(res)) // ERROR handling highly depends on your api
    );
  };

  if (controller) {
    setAbortMethod && setAbortMethod(() => controller.abort());
    return currentFetch();
  } else {
    const { cancelablePromise, cancel } = _makeCancelable(currentFetch);
    setAbortMethod && setAbortMethod(cancel);
    return cancelablePromise();
  }
};

["POST", "PUT", "DELETE", "PATCH", "GET"].forEach(
  el => (methods[el.toLowerCase()] = (...data) => _call(el, ...data))
);

export default methods;

Let’s start from the end.

I’ve exported an object that contains various functions (collected in the methods object) which one of these is the getToken that allows the developer to instruct the module on how to retrieve an eventual JWT token to pass in the headers.
You’d use it like this:

import Api from "./api.js";
Api.getToken = () => store.getTokenSelector(getState()); // example of a redux implementation
// or simply
Api.getToken = () => "token";

Then we bind through an arrow function the five strings (that denote the method property of the fetch option) to one main _call method wich will be the handler of all the calls we make.
These are the parameters:

  • method: it corresponds to the HTTP methods; you’ll never have access to this param
  • route: the part after the baseUrl we set after
  • params: an object key value of the params to pass (in GET it’ll be converted to query string)
  • setAbortMethod: the function to pass to obtain the abort method, (explanations down below)
  • auth: a flag to manually disable the passing of the token

The last “auth” param is there to have more fine-grained control over the headers added for authentication. I’ve encountered APIs that error out if passing a token when one isn’t required and considering that it’s not always convenient to edit or change the result of the getToken function, you can use this flag (it defaults to true).

Now the _call method.

The first thing that _call does is to check if the AbortController “constructor” exists. If so it instantiates a controller otherwise the controller gets a null value. Then we check if the request is a GET request and in that case, we build a query string. I decided to not go for the URLSearchParams here since if I don’t wrong in React Native isn’t supported yet.

We store the full URL in the url value and then we create the options object of our request. Here is where we pass the controller signal, the params, the token, and all the other headers we need.

We then wrap the fetch in order to postpone the execution of the promise. There is the following check for the existence of the AbortController and:

  • if it exists, call the setAbortMethod and then return the unwrapped fetch we wrapped a few lines above
  • if it doesn’t exist, we lend the wrapped fetch to the _makeCancelable a function that is the code analyzed in the last article and proposed by Facebook that will return the right promise to await for and the fake cancel method.

A note on the setAbortMethod

As anticipated earlier I had trouble finding the right way to return back the abort method to the user. The implementation I chose that you can see in the code, produces the following usage:

let cancel;
const data = await Api.get("someRoute/1", null, (c) => (cancel = c)); // awaits the data
cancel(); // cancel the request

Conclusion

This should be enough to let you get started to use this code. You’ll find more examples in a react ecosystem in a small repo I’m about to prepare.