还在用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使用起来都无缝衔接 ~

相关推荐
程序猿小D19 分钟前
第二百六十七节 JPA教程 - JPA查询AND条件示例
java·开发语言·前端·数据库·windows·python·jpa
奔跑吧邓邓子1 小时前
npm包管理深度探索:从基础到进阶全面教程!
前端·npm·node.js
前端李易安1 小时前
ajax的原理,使用场景以及如何实现
前端·ajax·okhttp
汪子熙2 小时前
Angular 服务器端应用 ng-state tag 的作用介绍
前端·javascript·angular.js
Envyᥫᩣ2 小时前
《ASP.NET Web Forms 实现视频点赞功能的完整示例》
前端·asp.net·音视频·视频点赞
Мартин.6 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。7 小时前
案例-表白墙简单实现
前端·javascript·css
数云界7 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd7 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常7 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine