loading...
Cover image for Fetch() is All you Need

Fetch() is All you Need

ruheni profile image Ruheni Alex ・Updated on ・8 min read

Now I know what you are thinking... puppies are cute right?

No, Okay.

It's a little overkill to always reach out for a third-party library to handle data fetching for simple use cases when you have a fetch. axios and apollo-client are terrific libraries for handling requests. I've used them and I love them too. The purpose of this article is to show you how you an alternative way you can make the requests using fetch. I was mind-blown by some of the concepts I learnt in the process of writing this article. Some of the shortcomings of fetch are: it doesn't support network interceptors and won't work well if your web application is server side rendered without isomorphic-unfetch.

Before you install a package to help you make requests, let me show you some of the nifty features fetch has to offer.

A quick history lesson - XMLHttpRequest

Before fetch became a standard, we had XMLHttpRequest. No, it had nothing to do with fetching only XML from the server. It works with any type of data being sent to or from a server. It works both asynchronously or synchronously. This is because JavaScript is single-threaded and you don't want to block the main thread. Your web application will be unusable and whoever will review your code will get a little riled up and probably hunt you down. Please don't do that.

I should clarify that XMLHttpRequest is still supported in all browsers. Caveat, I've used this XMLHttpRequest twice. First time when I was learning how to make network requests in Js and at the time this article was being writtenπŸ™ˆ .

I found a cave painting of how a request is made using XMLHttpRequest. It looks something like this:

let request = new XMLHttpRequest()

request.open('GET', 'http://random-url-on-the-internet.lol', true)

request.onload = () => {
    let data = JSON.parse(this.response)
    console.log(data)
}

request.onerror = () => {
    // handle non-HTTP errors e.g. losing connection
    console.log(`Error occured: ${request.status}`)
}

request.send()
Enter fullscreen mode Exit fullscreen mode

This makes my head hurt every time I look at it. It's probably what inspired Matt Zabriskie to author axios. It can be a little tedious creating a new instance of XMLHttpRequest every time you wish to make a request. Keep in mind that, we haven't set headers or tried out other types of requests.

There are a couple more methods provided by XMLHttpRequest such as abort(), and setRequestHeader(). You can explore them in the MDN Documentation

So, fetch eh?

Since I've shown you what a network request using XMLHttpRequest looks like, here's how it looks like using Fetch()

const request = async () =>
    await fetch('http://random-url-on-the-internet.lol')
        .then(res => res.json())
        .then(console.log)
        .catch(console.error)

request()
Enter fullscreen mode Exit fullscreen mode

Looks fairly easy, right? πŸ˜‰

We have created an arrow function request() that is async. request() returns a Promise and we have to await it as well, just to make sure we don't block the main thread running on the browser.

The first argument is the URL to your API. By default, all requests made are 'GET'. More on how to make a 'POST' in the next section. The second argument, which is optional is an object containing the details of the request, such as the method, headers, cors policy and content-type.

.then() method is chained to the request because it is a Promise. This means once the request is complete, we execute something. In our case, we convert the response to JSON. The second .then() logs the data to the console. If there is an error exception .catch() will capture it.

Fetch is supported in all major browsers, except IE. Why won't you just accept your fate IE?

Request metadata

Fetch accepts a second parameter, the request options that is an object. It allows you to control a number of settings such as request headers, body, cors and cache. Let's look at an example where we make a 'POST' request, attach a token to the Authorization header and set the content type to application/json:

const options = {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer xxxxx-token-here'
    }
}

const request = async () =>
    await fetch('http://random-url-on-the-internet.lol', options)
        .then(res => res.json())
        .then(console.log)
        .catch(console.error)

request()
Enter fullscreen mode Exit fullscreen mode

If you would like to look into more options, MDN takes a deep dive into using Fetch.

Fetch from REST APIs

This is probably the simplest of the bunch and it will seem intuitive. I used jsonplaceholder.typicode.com API to demonstrate how to make network requests. Some APIs may require you attach an API key or a token to the request. The examples provided should give you a solid background on how to use fetch effectively.

GET requests

'GET' are pretty straightforward since

const requestSomeData = () => {
    fetch('https://jsonplaceholder.typicode.com/posts/1')
        .then((response) => response.json())
        .then((json) => console.log(json))
}

requestSomeData()
Enter fullscreen mode Exit fullscreen mode

POST requests

Create an options object in which you will specify the method is 'POST' and set the request body. Depending on the API you are using, you will probably need to send the body in JSON format.

const options = {
    method: 'POST',
    body: JSON.stringify({
        title: 'A Fresh Start',
        body: 'Maybe it is time you should consider of switching careers',
        userId: 1,
    }),
    headers: {
        'Content-type': 'application/json; charset=UTF-8',
    }
}

const postSomeData = () => {
    fetch('https://jsonplaceholder.typicode.com/posts', options)
        .then((response) => response.json())
        .then((json) => console.log(json))
}
Enter fullscreen mode Exit fullscreen mode

If you would like to make PUT, PATCH or DELETE requests, all you will need to do is specify the method in the request options

Fetch from GraphQL APIs

GraphQL requests are HTTP requests. Requests made to a GraphQL API are POST requests. Set the content type to application/json.

For the examples below, I created a sample GraphQL API hosted on Codesandbox. The data is stored in memory.

If you would like to fork it and play around with it, you can find it here. The API will allow you to request for books, create and books.

Queries

Queries define the information a client sends to a server, describing what they need.

Define the query and include it in the request body in JSON.

const url = 'https://3l097.sse.codesandbox.io/'

const GET_BOOKS = `
    query {
    books {
      id
      title
      author
      published
    }
}`

const querySomeData = () => {
    fetch(url, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ GET_BOOKS })
    })
        .then(res => res.json())
        .then(({ data }) => console.log(data))
        .catch(error => {
            console.log('Something happened.!πŸ’”', error)

        })
}

querySomeData()
Enter fullscreen mode Exit fullscreen mode

Mutations

Mutations are responsible for modifying data in a GraphQL API. Similar to what POST, PUT and DELETE do in a REST API.

Define your mutation and add variables that would represent data captured from a form, for example. A mutation allows you define the data you would like to be returned once its execution is complete.

const url = 'https://3l097.sse.codesandbox.io/'

const CREATE_BOOK = `
    mutation($title: String!, $author: String!, $description: String!) {
        createBook(
        title: $title,
        author: $author
        description: $description
    ){
        id
        title
        author
        description
    }
}`

const mutateSomeData = () => {
    fetch(url, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
            query: CREATE_BOOK,
            variables: {
                title: "I'm already tired of Fetch",
                author: "Ruheni Alex",
                description: "You should read this one"
            }
        })
    })
        .then(res => res.json())
        .then(console.log)
        .catch(console.error)
}

mutateSomedata()
Enter fullscreen mode Exit fullscreen mode

I highly encourage you to inspect the requests in the network tab using the browser devtools to understand what is going on under the hood.

Fetch on Window Focus

I never knew one could request data by focusing on a tab or window. Turns out it has nothing to do with fetch. But it's a pretty neat feature to include in your application.

This is especially helpful when a user leaves your application and data gets stale. When the user gets back to your application, data will be fetched and existing

const fetchSomeData = () => {
    fetch('https://jsonplaceholder.typicode.com/todos/1')
        .then(response => response.json())
        .then(json => console.log(json))
}

window.addEventListener('focus', fetchSomeData)
Enter fullscreen mode Exit fullscreen mode

Fetch Retries

Let's face it. Requests are bound to fail at some point. You can improve the user experience in your web application by making the request a couple more times before informing the user what went wrong. It's going to be a recursive function that will call itself until it runs out of retries.

const retryFetch = async (url, options = {}, retries = 5) => {
    const response = await fetch(url, options)
        .then(res => {
            if (res.ok) return res.json()

            if (retries > 0) {
                return retryFetch(url, options, retries - 1)
            } else {
                throw new Error(res)
            }
        })
        .catch(console.error)

    return response
}
Enter fullscreen mode Exit fullscreen mode

Fetch Wrapper

You can make a custom fetch function that would work for all types of requests. This is a concept I learnt from Kent C. Dodds. Now, my example isn't be polished, but I'm sure you can customize and add whatever would tickle your fancy.

const customFetch = (url, { body, ...customConfig }) => {
    const headers = {
        'Content-Type': 'application/json'
    }

    if (body) {
        return config.body = JSON.stringify(body)
    }

    const config = {
        method: body ? 'POST' : 'GET',
        ...customConfig,
        headers: {
            ...headers,
            ...customConfig.headers
        }
    }

    return window.fetch(url, config)
        .then(async res => {
            const data = await res.json()

            if (res.ok) {
                return data
            } else {
                return Promise.reject(data)
            }
        })
}

export { customFetch }
Enter fullscreen mode Exit fullscreen mode

Cancelling requests

Turns out, you can cancel a request. Yeah, I didn't know about it too. I came across this feature as I was reading the react-query docs. At first, I thought it was a library specific feature, but after some research, it's natively supported in the browsers. It's fairly new to me and I may make a lot of mistakes but feel free to explain it further to me.

Why do you need this? You don't. Fetch returns a promise which has 3 states: fulfilled, rejected and pending. There is no way you can cancel an ongoing fetch. It comes in handy when a user decides an action isn't needed anymore.

First, create a controller instance from AbortController(). controller has a single method, abort() and one property signal that allows you to set an event listener to it. signal is then added to the request options. In the example below, I created a timer to invoke abort() method after 100ms. This will throw an error to the console.

Note this is still an experimental technology.

const controller = new AbortController();
const signal = controller.signal;

let url = 'https://jsonplaceholder.typicode.com/todos/1'

setTimeout(() => controller.abort(), 100);

const fetchSomeData = () => {
    fetch(url, { signal })
        .then(res => res.json())
        .then(data => console.log(data))
        .catch(error => {
            if (error.name = 'AbortError') {
                console.log('You just aborted a fetch!πŸ’”')
            }
        })
}

fetchSomeData()
Enter fullscreen mode Exit fullscreen mode

Learn more

Replace axios with a custom fetch wrapper by Kent C. Dodds. In this article

Using Fetch

Add Retries to your API calls

Cover Photo by Rob Fuller on Unsplash

Discussion

pic
Editor guide
Collapse
ecyrbe profile image
ecyrbe

I have to desagree on some points because axios is a much more powerfull request layer.

  • First, there are no fetch interceptors, you'll have to use another library for that.
  • Second, fetch does not exist on node, you'll have to use node-fetch emulating library.
  • Third, node-fetch has no proxy handling on node
  • Fourth, fetch error handling is a pain, you'll have to do extra work to extract it and handle edge cases.
  • Fifth, timeout handling and cancellation handling is so much more verbose with fetch.

All these points explain why so many of us prefer using a higher layer library like axios.

Collapse
ruheni profile image
Ruheni Alex Author

Hi! I totally agree with you.

I've noticed the tone I used was a little condescending. Sorry about that. I've made an update clarifying the intention of writing the article and some of the shortcomings of fetch. πŸ™‚

Didn't mean to rattle youπŸ˜…

Collapse
ecyrbe profile image
ecyrbe

Hello Alex,
No worries. I was not bothered at all by the tone of your post.
You have written a good article, with a lot of insights for developpers.
Keep it up.

Collapse
1e4_ profile image
Ian

Yeh agreed. Fetch isn't a replacement at all for axios, axios simply provides so much more than just fetching something. +1 for interceptors I was going to mention but you beat me too it :)

Collapse
mtee profile image
Margaret W.N

Cave paintingπŸ˜‚πŸ˜‚. Woow!

Collapse
etienneburdet profile image
Etienne Burdet

Yep, there is a reason why SSR frameworks still favor Nuxt. It's hell with fetch, there's is always calls responding weirdly.

Collapse
ruheni profile image
Ruheni Alex Author

How does isormophic-unfetch perform in SSR using Nuxt?

Thread Thread
etienneburdet profile image
Etienne Burdet

That's a good question! Never tried it, but seems very attractive. Very minimalist.
Zapper has a nice approach: replacing fetch or this.fetch as needed (being a compiler it's easy enough to do). Effectively, you're just writing you're only using fetch.

Collapse
thomasferro profile image
Thomas Ferro

Nice article but beware of the response.json() call before checking reponse.ok, it may get you or anyone copying your example in trouble!

I found this video by Joel Thoms to explain clearly the issue: youtube.com/watch?v=aIboXjxo-w8

Collapse
ruheni profile image
Ruheni Alex Author

Learnt something new today! Thank you for sharing

Collapse
reyronald profile image
Ronald Rey

There's one specific case I'd like to point out where XMLHttpRequest could be preferred, and is regarding cancellations.

When you abort a fetch call, you are aborting the network call, but the Promise itself is not canceled because Promises don't support cancelation. As you can see, the actual behavior is that the promise throws.

This is fine for most cases and practically all product development, but in very specific and extreme circumstances this might not be desired because Promise ticks are actually expensive and can clog the stack, which can end up having a consequence in the execution flow of the program.

A callback-oriented API that doesn't rely on Promises doesn't have this drawback, and that just wouldn't be possible to do with fetch, but is possible to do with XMLHttpRequest.

Collapse
michaelcurrin profile image
Mike

Thanks I was actually using axios yesterday and wondering how to replace with fetch.

Why don't you open the post with dogs are cute instead of cows are cute? Otherwise it seems like a distraction

Collapse
ruheni profile image
Ruheni Alex Author

I'm glad you liked it. Sure, I'll make the update πŸ˜‰

Collapse
youssefzidan profile image
YoussefZidan

Wow that's amazing!
How about interceptors?
Does fetch support it?

Collapse
1e4_ profile image
Ian

No. Fetch is very minimal and not a replacement for axios at all. Unless you are just grabbing a few things

Collapse
nimicent profile image
Julia Nash

So glad to see this so well received by the community!