fetch 在实际项目中的思考

fetch 在实际项目中的思考

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

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

错误处理

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

js 复制代码
// 比如 axios 的写法

import axios from "axios";

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

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

js 复制代码
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。如果我们想在服务端响应异常的时候返回一个拒绝状态,就需要额外添加一些代码:

js 复制代码
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 去发送,只需要简单的一行:

js 复制代码
axios.post('http://localhost:3000/user', {
  name: 'leo'
})

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

js 复制代码
fetch("http://localhost:3000/user", {
  method: "POST",
  body: {
    name: "leo",
  },
})

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

改写如下可以正常使用:

js 复制代码
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 请求中都需要设置如下:

js 复制代码
fetch(url, {
  credentials: "include",
  mode: "cors",
  headers: {
    Accept: "application/json",
  },
});

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

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

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

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

总结

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

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

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

js 复制代码
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 不是非常适用,直接使用封装好的工具可能是更好的选择。

相关推荐
北岛寒沫15 分钟前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
everyStudy18 分钟前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
无心使然云中漫步2 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者2 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_2 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋3 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120533 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢3 小时前
【Vue】VueRouter路由
前端·javascript·vue.js
随笔写5 小时前
vue使用关于speak-tss插件的详细介绍
前端·javascript·vue.js
快乐牌刀片885 小时前
web - JavaScript
开发语言·前端·javascript