Original link:
Why I won’t be using the Fetch API in my apps


By Shahar Talmi

I was thrilled when the FETCH API became a Web standard, because I no longer had to use some HTTP tool library to do HTTP requests. XMLHttpRequest is too low-level and difficult to use (even its name is weird, why does XML not capitalize Http??). . You’ll have to wrap it yourself, or choose from a bunch of packaged alternatives like jQuery’s $. Ajax, Angualr’s $HTTP, SuperAgent, and my favorite, Axios. However, are we really done with the HTTP tool library?

With FETCH, I no longer have to choose from a large library of tools or argue with colleagues about which one is the best. All I had to do was introduce a Fetch Polyfill, and I was happy to use the standard API, which was designed from numerous use cases and lessons learned.

However, when we look at some very basic real-world scenarios, we can see that the HTTP tool library is useful. Fetch is a popular new feature that makes it easy to do low-level operations, and it’s designed for that. As a low-level API, it should not be used directly in most applications, although it is more reasonably abstract.

Error handling

In some simple FETCH examples, FETCH looks great, similar to the HTTP tool library we’re used to. Take this example using Axios:

axios.get(url)
  .then(result = > console.log('success:'. result))
  .catch(error = > console.log('error:'. error));
Copy the code

We can rewrite fetch as

fetch(url).then(response = > response.json())
  .then(result = > console.log('success:'. result))
  .catch(error = > console.log('error:'. error));
Copy the code

Simple, right? Careful readers may notice that we need to add a response.json() to get the data from the Response stream object, but that’s a small price to pay. I personally believe that the need to respond to a stream is a special case, and in general, WHEN I design apis, I don’t let the special case interfere with the general case, preferring to allow users to provide a flag indicating whether or not they need a stream rather than shoehorning them with a stream object. But overall, it’s not a big deal.

The really important part of the above example that you may not have noticed (as I did when I first used fetch) is that the two pieces of code are not actually doing the same thing at all! All of the HTTP libraries I mentioned earlier treat a response with an incorrect status code (such as 404,500, etc.) as an error, while FETCH, like XMLHttpRequest, only works in the case of a network error (such as an IP address that cannot be resolved, The server is unreachable or does not allow CORS) reject.

This means that when the server returns 404, the second piece of code will print ‘success’. If we want the code to behave more intuitively, and get a reject promise when the server returns an error code, we need to do something like this:

fetch(url)
  .then(response = > {
    return response.json().then(data = > {
      if (response.ok) {
        return data;
      } else {
        return Promise.reject({status: response.status. data});
      }
    });
  })
  .then(result = > console.log('success:'. result))
  .catch(error = > console.log('error:'. error));
Copy the code

I’m sure a lot of people are asking, “What? You make a request to the server and get a response, whether it’s a 404 or not, it’s a response from the server. Why treat it like a network error?” They’re right. It’s just a matter of perspective. I think from a developer’s perspective, an error response should be treated as an exception, just like a network error. To fix this behavior of the FETCH, we have to do this because there is no way to change the standard behavior of the FETCH. Clearly we need a more appropriate abstraction for developers.

A POST request

Another very common situation is to make a POST request to the server. With axios, we can write:

axios.post('/user'. {
  firstName: 'Fred'.
  lastName: 'Flintstone'
});
Copy the code

I was so optimistic when I first started using the FETCH API, I thought: Great, this new API is so similar to what I’m used to. However, I ended up wasting almost an hour before successfully issuing a POST request because the code didn’t work:

fetch('/user'. {
  method: 'POST'.
  body: {
    firstName: 'Fred'.
    lastName: 'Flintstone'
  }
});
Copy the code

I’m sure a lot of people, like me, have had the painful experience of realizing that FETCH is an underlying API that doesn’t make it easy for us to deal with this general situation, and you have to use it unambiguously. First, JSON must be converted to a string, and then the ‘Content-Type’ header must be set to indicate that the entity Type is JSON, otherwise the server will treat it as a normal string. We should write:

fetch('/user'. {
  method: 'POST'.
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    firstName: 'Fred'.
    lastName: 'Flintstone'
  })
});
Copy the code

Well, this code is way too long for me to write every time I use the FETCH API. But as you will see, we have to write more!

The default behavior

As you can see, you must use fetch clearly and explicitly, if you don’t specify what you want, you get nothing. For example, none of the fetch calls mentioned above could get data from my server because:

  1. My server uses cookie-based authentication, and FETCH does not send cookies by default
  2. My server needs to know if the client can handle JSON data
  3. My server is under a different subdomain and FETCH does not have CORS enabled by default
  4. To defend against XSRF attacks, my server requires that every request must carry an X-XSRF-Token header to prove that the request was actually made from my own page

So, I should write:

fetch(url. {
  credentials: 'include'.
  mode: 'cors'.
  headers: {
    'Accept': 'application/json'.
    'X-XSRF-TOKEN': getCookieValue('XSRF-TOKEN')
  }
});
Copy the code

I can’t say that fetch’s default behavior is a problem, but if I want to make requests from multiple places in my application, I need a mechanism to change this default behavior so that FETCH works in my application. Unfortunately, FETCH does not have this mechanism to override the default behavior. As you might have guessed, axios has this:

axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Accept'] = 'application/json';
axios.defaults.headers.post['Content-Type'] = 'application/json';
Copy the code

But this is just for demonstration purposes, because in fact all the features mentioned above, including XSRF defense, are provided by axios by default. Axios was designed to provide an easy-to-use tool for making requests to the server, while FETCH had to be designed to be more generic, which is why it wasn’t the best tool to do the job.

conclusion

Assuming you don’t use an HTTP library, that means more than writing a line of code like this:

function addUser(details) {
  return axios.post('https://api.example.com/user'. details);
}
Copy the code

You have to write:

function addUser(details) {
  return fetch('https://api.example.com/user'. {
    mode: 'cors'.
    method: 'POST'.
    credentials: 'include'.
    body: JSON.stringify(details),
    headers: {
      'Content-Type': 'application/json'.
      'Accept': 'application/json'.
      'X-XSRF-TOKEN': getCookieValue('XSRF-TOKEN')
    }
  }).then(response = > {
    return response.json().then(data = > {
      if (response.ok) {
        return data;
      } else {
        return Promise.reject({status: response.status. data});
      }
    });
  });
}
Copy the code

Repeating so much code every time an API is called is obviously not a good idea. Instead of using fetch directly, you might extract a function from it and hand it to a colleague on a project.

When you move on to the next project, you might want to wrap that function further into a library. Then, as more requirements come in, you try to streamline your API, make it more flexible, fix some bugs, or make your API consistent. You may also add some new features, such as interrupt requests, custom timeouts, etc.

You might get a really great job done. But all you did was create another HTTP tool library to use in your project instead of the FETCH API. Just type NPM install –save axios, or install another HTTP library of your choice. This will save you a lot of time and effort.

Also, if you think about it, do you care if the HTTP library internally uses FETCH or XMLHttpRequest?

P.S.

I just want to reiterate: I’m not saying fetch is bad! I don’t think the points mentioned above are flaws in the fetch API design, which makes perfect sense for an underlying API. I just don’t recommend using a low-level API like FETCH directly in an application. People should use tools that abstract the bottom layer and provide a high-level API that will better suit their needs.