fetch 在实际项目中的思考

fetch 在实际项目中的思考

随着 fetch 的出现,可以让我们不在需要在项目中导入一些三方库来发送 http 请求。虽然浏览器中早已存在 XMLHttpRequest,但是由于它的写法比较繁琐,我们在使用的通常都是导入一些依赖库来更方便的使用它,比如 jquery 的 $.ajax()、angular 的 $http,以及最常用的 axios。但是 fetch 在实际项目中真的好用吗?

虽然我们确实可以通过 fetch 更容易的操作一些底层的操作,但是在实际项目中使用抽象的、简便的方法其实真的会比直接操作底层 API 会更省力。

错误处理

我们在学习 fetch 的基础写法会发现,它看起来非常像我们常用的依赖库的写法:

// 比如 axios 的写法

import axios from "axios";

axios
  .get("http://localhost:3000/api")
  .then((result) => console.log("success:", result))
  .catch((error) => console.log("error:", error));

通过 fetch 改写可以写成如下:

fetch("http://localhost:3000/api")
  .then((response) => response.json())
  .then((result) => console.log("success:", result))
  .catch((error) => console.log("error:", error));

看起来 fetch 只是多了一行将响应流解析成 JSON 格式的代码,看起来问题不是很大。

但是我们在认真看一下,或者实际上调用一下就会发现,它们是不一样的,前面提到的多种三方库在处理错误状态码(比如 404、500 这些)的时候都会当成是一个错误。但是 fetch 只会在网络异常(ip 解析异常、服务无法访问或者 cors)的时候才会拒绝这个 Promise,之后在 catch 中捕获。

这也就意味在上面的代码中,如果我们的响应状态码是 404,fetch 代码段还是会打印 success。如果我们想在服务端响应异常的时候返回一个拒绝状态,就需要额外添加一些代码:

fetch("http://localhost:3000/api")
  .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));

ok 的值只在响应码处于 200 - 299 的范围内才返回 true

可能很多人会觉得这没什么,即便从服务端返回的是 404 这样的状态码,也是从服务端获取的数据,说明服务端确实成功响应了。这其实是一种观点,没有什么对错之分,只不过在我看来服务端返回的错误响应还是应该认为是一种异常,但是我们没办法修改 fetch 的规范,我们只是应该存在一种更好的抽象去表示。

POST 请求

另一种常用的场景就是发送 POST 请求,当我们使用 axios 去发送,只需要简单的一行:

axios.post('http://localhost:3000/user', {
  name: 'leo'
})

但是在 fetch 中,我们却需要使用定义一堆东西,简单的设置 method、body 并不能成功请求,比如:

fetch("http://localhost:3000/user", {
  method: "POST",
  body: {
    name: "leo",
  },
})

这种简单写法发送给服务端的数据是异常的([object Object])。fetch 的 API 是非常显式的,对于发送的是 JSON 数据,我们必须把这个数据转成字符串,并添加 Content-Type: application/json 头来告诉服务端这个 payload 是 JSON,否则服务端会认为它是一个字符串。

改写如下可以正常使用:

fetch("http://localhost:3000/user", {
  method: "POST",
  headers: {
    'Content-Type': 'application/json'
  }
  body: JSON.stringify({
    name: "leo",
  }),
})

假设我们有非常多类似的请求,那就要重复写这些配置。

fetch 默认设置

如前面所说,fetch 是一种非常显式的 API,如果我们不主动设置,就不能拿到任何东西。在实际项目中,可能存在以下问题:

  1. fetch 默认是不会发送 cookie 的,如果我们的项目中依赖 cookie 来做验证就需要手动进行配置。
  2. 需要手动设置 ‘Accept: application/json’ 表明客户端可以处理 JSON 数据。
  3. fetch 默认是不支持 cors 的

所以我们在 fetch 请求中都需要设置如下:

fetch(url, {
  credentials: "include",
  mode: "cors",
  headers: {
    Accept: "application/json",
  },
});

看起来多加点配置也没什么,但是如果我们的项目中每个请求都需要添加这些呢?fetch 并没有提供可以修改默认的方法。

axios 提供了这种修改默认配置的方法,非常简便:

axios.defaults.baseURL = 'http://localhost:3000';
axios.defaults.headers.common['Accept'] = 'application/json';
axios.defaults.headers.post['Content-Type'] = 'application/json';

axios 的目标是为了能简单的调用 api 发送请求。而 fetch 的目标更远大,并不是很适合我们目前的项目。

总结

如果我们不想要导入三方库来发送请求,意味着我们就不能通过下面这一行代码实现:

function addUser(details) {
  return axios.post('http://localhost:3000/user', details);
}

而是需要封装一个这样的方法:

function addUser(details) {
  return fetch('http://localhost:3000/user', {
    mode: 'cors',
    method: 'POST',
    credentials: 'include',
    body: JSON.stringify(details),
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    }
  }).then(response => {
    return response.json().then(data => {
      if (response.ok) {
        return data;
      } else {
        return Promise.reject({status: response.status, data});
      }
    });
  });
}

这个一看就知道在项目中是不应该存在大量这样的重复代码,我们可能需要将更多的场景考虑进来,然后进行一个封装。

那么当我们有另一个项目也需要这个封装函数的时候,是不是又要去修改、优化,来让这些 api 变得更简便、
适用。但是在这个过程中我们是不是又创建了一个发送请求的库,而不是直接使用 fetch 这个 api 呢?

当然,本文并不是在否认 fetch 是一个无用的设计,fetch 的底层设计非常好,比起 XMLHttpRequest,它能让我们对请求进行更细的控制。只不过在日常项目中这种底层 api 不是非常适用,直接使用封装好的工具可能是更好的选择。

  • 12
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值