Getting Started with JavaScript Fetch API
Tags: javascript, tutorial, api, beginners
Published: 2022-12-05 06:07:09

## Prelude
Have you ever used `XMLHttpRequest` object in JavaScript to send a request to the backend to fetch or post some data? If not, then lucky you, as it was pretty chaotic and complicated. 

Here's a quick example of what it takes to fetch a file from a given URL using `XMLHttpRequest` object vs `$.ajax()` in jQuery vs newly introduced native `fetch` API.

---

### Fetching Data using `XMLHttpRequest`

```javascript
var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function() {
  if (xhr.readyState == XMLHttpRequest.DONE) {
    console.log(xhr.responseText);
  }
}

xhr.open('GET', 'https://api.jsonbin.io/v3/b/6360e54a65b57a31e6a94a42', true);
xhr.send(null);
```

Looks tedious right? That's not it, the above is mostly a minimal example of how to send a request using `XMLHttpRequest()` object. Next, jQuery comes to the rescue by introducing `$.ajax()`, a simplified version of the `XMLHttpRequest()` as a cherry on top, introducing additional functionalities.

---

### Fetching Data using `$.get()`

```javascript
$.get(
  'https://api.jsonbin.io/v3/b/6360e54a65b57a31e6a94a42',
  (res) => {
    console.log(res);
  }
);
```

jQuery simplified fetching the data to a great extent, compared to the native `XMLHttpRequest()` object. Though it was easy, it came at some cost, as the jQuery lib weighed ~100kb (vague estimation) where even if you built the lib with ajax only module, it still weighed roughly ~50kb which is pretty high to do something as basic as fetching some JSON data.

Before we dive into using `fetch()` API, here's something important to take a note of:

> The fetch specification differs from `jQuery.ajax()` in the following significant ways:

> 1. The Promise returned from `fetch()` won't reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, as soon as the server responds with headers, the Promise will resolve normally (with the ok property of the response set to false if the response isn't in the range 200–299), and it will only reject on network failure or if anything prevented the request from completing. <sup>[[1]](#response_failure)</sup>

> 2. Unless `fetch()` is called with the `credentials` option set to `include`, `fetch()` <sup>[Quoting from MDN](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)</sup>:
  - Won't send cookies in cross-origin requests.
  - Won't set any cookies sent back in cross-origin responses.


---

## Introducing the fetch() API

`fetch` API is supported by most of the modern browsers today. It makes it easier for the devs to make asynchronous network requests as it's promise based, thus, avoids callback hell.

Here's a simple example of making a `GET` request using `fetch` API.

```javascript
fetch('https://api.jsonbin.io/v3/b/6360e54a65b57a31e6a94a42')
  .then((res) => res.json())
  .then((data) => console.log(data));
```

You could also use `async ... await` instead of promises, like:

```javascript
(async () => {
  const res = await fetch('https://api.jsonbin.io/v3/b/6360e54a65b57a31e6a94a42');
  const data = await res.json();
  console.log(data);
})();
```

In the above examples, `fetch()` takes one argument which is the path to the resource you wish to fetch. An important thing to note here, the response does not return the JSON response body but it instead a representation of the entire HTTP response where you could use `.json()` to fetch the actual response JSON data.

`fetch` also provides various options which you could use for the response object, like, `text()` which returns raw text, `formData()`, `blob()`, etc.

<hr id="response_failure" />

## Error Handling
When I first tried fetch, I used `.catch()` assuming that my code would end up in the `.catch()` statement if the request was a non 200. To my surprise, `.catch()` never ran, even if the request failed with a response code of 4xx or 5xx. As mentioned previously in this post.

For eg, the following URL would throw a 404 error.

```javascript
fetch('https://api.jsonbin.io/v3/b/6360e54a65b57a31e6a94a4a')
  .then((res) => res.json())
  .then((data) => console.log(data))
  .catch((err) => console.log('Something went wrong'));
```

Here, my code didn't print `Something went wrong`. Later, referring to the official documentation, it turns out that fetch doesn't throw an error if the server returns an error (4xx or 5xx).

The only time my code would end up in the catch statement is when there's no internet connection, or similar issues. Here's where `response.ok` comes to the rescue. Using `response.ok` would return true if the response status code is between `200-299` else false.

In the above example, using `response.ok` to check the request state would return `false` as the response code was `404`.

```javascript
(async () => {
  const res = await fetch('https://api.jsonbin.io/v3/b/6360e54a65b57a31e6a94a42');
  if(!res.ok) {
    throw new Error(`'Request failed with status code: ${res.status}`);
  }

  const data = await res.json();
  console.log(data);
})();
```

---

## Attaching Request Headers & Payload
In the above examples, we tried to fetch these records using the **default** `GET` method.

But what if you wish to send a `POST` or a `PUT` request along with some `Headers` attached to it?

You could pass `headers` and attach a payload to your `fetch` request either by using [`Headers()`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object or you could simply pass these in an object as a second parameter to the `fetch` API like:

```javascript
(async () => {
  const res = await fetch('https://api.jsonbin.io/v3/b/6360e54a65b57a31e6a94a42', 
  {
    method: 'POST', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, *cors, same-origin
    headers: { // Request headers
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({"sample": "json"}) // Request payload
  });

  if(!res.ok) {
    throw new Error(`'Request failed with status code: ${res.status}`);
  }

  const data = await res.json();
  console.log(data);
})();
```

---

## `fetch()` vs. XHR
There are a few differences between the `fetch` API and XHR request. I didn't want to repeat or copy these since I myself got to know about these differences from a [Stackoverflow answer](https://stackoverflow.com/q/35549547/1542290).

---

## Browser Support
`fetch()` API now has a decent Browser Support. Here's a quick Browser Support overview from MDN:

![Browser Support for fetch() JavaScript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1av2yhm0ji6l9tfbsnoz.png)

You could also use `fetch` in NodeJS using `--experimental-fetch` flag, or use the [node-fetch](https://www.npmjs.com/package/node-fetch) package if needed.

---

## Resources
- [Using the fetch() API on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
- [Fetch API vs. XHR](https://stackoverflow.com/questions/35549547/fetch-api-vs-xmlhttprequest)
- [Fetch API Browser Support on CanIUse](https://caniuse.com/fetch)
- [Introduction to fetch() API](https://web.dev/introduction-to-fetch/)