Skip to content

请求库封装

背景

axios 虽然很成熟,但它只是一个基础库,没有提供诸多的上层功能,比如:

  1. 请求重试
  2. 请求缓存
  3. 请求幂等
  4. 请求串行
  5. 请求并发

VueRequest(vue) / SWR(react) 它们虽然提供的功能很多,但仍然存在诸多问题:

  1. 与上层框架过度绑定导致开发场景受限,也无法提供统一的 API
  2. 成熟度不够,issue 的回复也难以做到及时,存在一定风险
  3. 它们没有聚合基础请求库,仍然需要手动整合

除此之外更重要的是:

公共库不包含公司内部制定的协议规范,即便使用公共库,也必须针对它们做二次封装。

综上,需要自行封装一套适配公司业务的前端请求库

方案和实现

库结构的宏观设计

整个库结构包含三层,从下往上 依次是:

  • 请求实现层:提供请求基本功能
  • request-core:提供网络上层控制,比如请求串行、请求并行、请求重试、请求防重等功能
  • request-bus:为请求绑定业务功能,该层接入公司内部协议规范和接口文档,向外提供业务接口 API

layer1


优化设计:

在三层中,请求实现层的实现有多种方式:

  • 基于 XHR 原生
  • 基于 fetch 原生
  • 基于 axios 等第三方库
  • ...

这种实现的多样性可能导致这一次层的不稳定,而 request-imp 是基础层,它的不稳性会传导到上一层。

所以必须寻求一种方案来隔离这种不稳定性。

我们可以基于 DIP(Dependence Inversion Principle,依赖倒置原则),彻底将 request-core 和请求的实现解耦,而 typescript 的类型系统让这一切的落地成为了可能。

layer2

请求缓存

请求缓存是指创建一个带有缓存的请求,当没有命中缓存时发送请求并缓存结果,当有缓存时直接返回缓存。

js
const req = createCacheRequestor()
req.get('/a') // 请求
req.get('/a') // 使用缓存
req.get('/b') // 请求
req.get('/b') // 使用缓存
// ...

要实现此功能需要考虑几个核心问题:

  1. 请求结果怎么存?
  2. 存在哪?
  3. 缓存键是什么?

我们希望用户能够指定缓存方案(内存/持久化),同时也能够指定缓存键:

js
const req = createCacheRequestor({
  key(config) {
    // config 为某次请求的配置
    return config.pathname // 使用 pathname 作为缓存键
  },
  persist: true, //是否开启持久化缓存
})

存储有多种方案:

  1. 内存
  2. storage
  3. cookie
  4. indexDB
  5. SW
  6. WebSQL

不同的方案,存储的格式不同,支持的功能不同,兼容性也不同。为了抹平这种差异,避免将来存储方案变动时对其他代码造成影响,需要设计一个稳定的接口来屏蔽方案间的差异。

storage

ts
export interface CahceStore {
  has(key: string): Promise<boolean>
  get(key: string): Promise<T>
  set(key: string, ...value: T[]): Promise<void>
  // ...
}

export function useCacheStore(isPersist): CacheStore{
  if(!isPersist) {
    return createMemoryStore();
  } else {
    return createStoragestore():
  }
}

请求幂等

幂等性是一个数学概念,常见于抽象代数

在网络请求中,很多接口都要求幂等性,比如支付,同一订单多次支付和一次支付对用户余额的影响应该是一样的。

要解决这一问题,就必须保证:要求幂等的请求不能重复提交

这里的关键问题就在于定义什么是 重复

我们可以把重复定义为:请求方法、请求头、请求体完全一致

样板代码

简历和面试

基于 MIT 许可发布