还在用Axios?来试试这个基于fetch的现代请求库——Ky

背景

最近做了个AI大语言模型的前端页面,类似ChatGPT的那种,其中用到了流式传输的功能,即:

前端发起HTTP请求,服务端响应的HTTP HeaderConent-Type的值为text/event-stream,这种传输方式称之为SSE (Server-Sent Events)

这是一个流式传输的数据,服务端可以一部分一部分的发送到前端,前端也可以一部分一部分的展示到页面中,就像这样:

(自行脑补ChatGPT的聊天回复)

不必像一般的HTTP请求那样等待所有数据下载完成再展示给用户,可以边传输边展示,用户体验较好。

使用浏览器内置的fetchAPI可以轻松实现SSE流式传输,具体实现方式可以自行搜索,不是本文的重点内容。

但由于项目中使用了axios作为请求库,就需要通过axios来实现,但经过查阅axios相关的文档、博客、GitHub issue 等,发现axios对其的支持不是太好,实现起来较为繁琐,因为axios的底层是基于XHR的,相对来说已经是一个比较古老的API了。

所以萌生了一个想法,为何不找一个基于fetch的现代请求库来替代axios呢?毕竟都2024年这个时候了,fetch的兼容性已经非常好了。

经过一系列的搜寻和对比,发现了这个十分优秀的请求库------ky

Ky 简介

Ky是一个基于浏览器Fetch API的,小型优雅的HTTP客户端,具有以下特点:

  • 零依赖,大小仅3.3K
  • 跨运行时,支持在浏览器、Node.js、Deno 等不同的运行环境中使用,理论上应该可以运行在任意兼容fetchAPI的环境中
  • 支持创建实例 (类似axiosaxios.create)
  • 支持请求前后的Hooks钩子函数 (类似axiosinterceptors 拦截器)
  • 使用比fetch更加的简单便捷
  • 支持请求失败后自动重试的功能
  • 支持超时设置
  • 支持URL前缀
  • 更多...... 详见KyGitHub主页

截至本文撰写的时候,KyGitHub Star数已经高达11.3K

顺带一提,这个作者还写了一个专门用于Node.js的请求库叫做Got,其GitHub Star数更是高达13.9K,大佬恐怖如斯。

Ky 使用

安装

shell 复制代码
npm install ky --save

基础使用

有种axiosfetch结合体的感觉,哈哈哈

ts 复制代码
import ky from 'ky';

const data = await ky.get('https://example.com/').json();
const text = await ky.get('https://example.com/').text();
const data = await ky.post('https://example.com/', { json: { username: 'xxx' } }).json();

创建实例

这里有两点需要注意:

  1. Ky默认会在请求出错后自动重试2次,可以通过配置retry: 0为来关闭自动重试;
  2. Ky实例在配置了prefixUrl (URL前缀)之后,在发起请求时请求的路径不能再以"/"开头,否则会报错,详见下方代码
typescript 复制代码
const httpClient = ky.create({
  prefixUrl: 'https://example.com/',
  retry: 0, // 请求出错时 不自动重试 默认会自动重试2次
});

/** 正确的写法 最终会请求 https://example.com/api/user */
const data = httpClient.get('api/user').json();

/** 以下是错误的写法! Ky会直接报错,告诉你配置了prefixUrl后请求路径不能以"/"开头 */
const data = httpClient.get('/api/user').json();

Ky Hooks 钩子函数说明 (类似axios的拦截器)

KyHooksaxios的拦截器类似,都可以在请求之前或之后做一些统一的处理。

如果你不了解axiosKyHooksNode.js中的中间件(Middleware)思路上也有些类似。

以下是Hooks钩子函数列表:

  • beforeRequest 请求发起请求之前调用的Hook,可以在这里为请求统一添加用户的token,类似axios的拦截器interceptors.request.use

  • beforeRetry 请求重试之前调用的Hook

  • beforeError 请求抛出HTTPError之前调用的Hook,可以在这里统一处理所有的HTTPError,减少具体业务逻辑中的错误处理代码

  • afterResponse 请求响应之后调用的Hook,可以对响应的数据做一些统一处理

  • 更多...... 详见KyGitHub主页

配置实例的Hooks 进行全局统一的错误处理

在我们的日常开发中,请求的错误一般分为2种:

1. 业务上的错误,比如用户无权访问此接口、执行此操作之前必须先完成另一个操作等业务错误;

2. HTTP错误,如请求超时等。

针对第2种错误,我们可以直接在beforeError函数中进行处理;

针对第1种错误,就要分情况讨论了:

(1) 有些项目的业务错误,不会更改响应的HTTP状态码,后端会通过返回的JSON数据中的code字段来区分,正常情况code就为200,业务错误的话code就为其它值,这样就可以在afterResponse函数中匹配code的值进行统一的错误处理。

(2) 还有些项目的业务错误会直接更改响应的HTTP状态码,这种错误就和第2种错误类似了,都属于HTTP的错误,自然也都是在beforeError函数中进行统一的错误处理。

我现在所处的项目,就属于第(1)种情况,以下是一个针对这种情况的Ky全局统一错误处理小示例,如果你的项目是第(2)种情况也可以参考其思路:

ts 复制代码
const httpClient = ky.create({
  prefixUrl: 'https://example.com/',
  retry: 0, // 请求出错时 不自动重试 默认会自动重试2次
  hooks: {
    /** 请求发起之前 要执行的额外操作 */
    beforeRequest: [
      /** 每次发起请求前 获取用户的token 添加到请求header中 */
      (request, options) => {
        const token = getUserToken();
        token && request.headers.set('Authorization', token);
      },
    ],
    /** 服务器响应之后 请求调用的函数resolve之前 要执行的额外操作 */
    afterResponse: [
      /** 每次响应之后 根据响应的code进行错误处理 */
      async (request, options, response) => {
        // 忽略HTTP状态码不为 200 的响应
        if (response.status !== 200) {
          return;
        }

        // 判断 如果响应头的 Content-Type 不是 JSON 则忽略
        if (!response.headers.get('Content-Type')?.toLowerCase().includes('application/json')) {
          return;
        }

        // 响应头的 Content-Type 是 JSON 继续进行处理
        let data;
        try {
          data = await response.json();
        } catch (error) {
          // JSON解析失败
          console.error('响应数据解析为JSON时出错:', error);
          message.error('响应数据解析为JSON时出错!');
          throw error;
        }

        // code 不存在 或不是 number 的情况 则忽略
        if (typeof data.code !== 'number') {
          return;
        }

        // 根据服务端返回的 code 进行不同的处理
        switch (data.code) {
          case 200: {
            // 200 请求成功
            // 不执行任何操作
            break;
          }
          case 401: {
            // 401 未登录 或者 token 过期
            // 退出登录
            signOut();
            break;
          }
          default: {
            // 其他错误
            const errMsg = data.msg || '请求出错';
            // 提示错误信息
            message.warning(errMsg);
            // 抛出错误 请求发送方法调用的位置可以捕获到此错误
            throw new Error(errMsg);
          }
        }
      },
    ],
    /** HTTP 请求发生错误时 在抛出错误之前执行的操作
     * (afterResponse中抛出的错误不会导致这个钩子执行 只会在HTTPError时执行)
     */
    beforeError: [
      async error => {
        console.error('HTTPError', error);
        message.error(error.message);
        // 返回要抛出的 HTTPError 对象
        return error;
      },
    ],
  },
});

结语

一段时间使用下来,感觉Ky基本上是可以代替掉axios的,而且Ky可以直接返回fetchResponse对象,进而可以基于Response对象进行一些高级的操作,比如上文中提到的SSE流式传输,可以充分利用fetchAPI的优势,并且结合了axios的许多优点。

PS: 而且还支持跨运行时使用,浏览器、Node、Deno使用起来都无缝衔接 ~

相关推荐
y先森3 分钟前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy3 分钟前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189116 分钟前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿1 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡2 小时前
commitlint校验git提交信息
前端
虾球xz3 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇3 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒3 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员3 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐3 小时前
前端图像处理(一)
前端