封装 Axios 来处理多层的接口数据

1,583 阅读5分钟

背景

我在掘金看了很多对 Axios 的封装,各有各的封装目的,今天我来讲下我遇到的一些痛点,及我是怎么解决的。

这些年对接了很多个后端及不同的后端接口,返回的格式各式各样,不过基本上可以归纳为对返回的数据进行了业务封装,HTTP 统一响应 200,然后在响应体里面添加业务代码。但大部分的业务代码就 2 个:成功(0)、失败(1)

比如这样的:

const successResponse = {
  status: 200,
  statusText: 'ok',
  data: {
    code: 0,
    message: '成功',
    result: { /* 真正的数据 */ }
  }
}

const errorResponse = {
  status: 200,
  statusText: 'ok',
  data: {
    code: 1,
    message: '出错了'
  }
}

还有比较离谱的,比如响应码还返回了其他比如 500400 等,导致不仅要判断响应码,还要判断业务码。

所以呢,很多开发人员会编写如下的代码,明明响应成功了还要判断是否有业务错误,并且在没有封装的情况下,需要在各个业务都重复写相同的代码

someServiceApi()
  .then((res) => {
    if (res.code === 0) {
      console.log(res.result)
    } else {
      console.log(res.message || '出错了')
    }
  }, (e) => {
    console.log(e.message || '出错了')
  })

所以我希望能编写 Axios 的拦截器来处理以下问题:

  • 后端接口数据的多重封装
  • 后端接口的响应错误的不同形式
  • 前端有请求处理没有提取重复的逻辑

希望在业务页面能做到:

  • 只需调用接口方法
  • 只需获取数据
  • 不关心是否出错
  • 不关心是否需要弹出错误信息

解决方案

下面是我的方案简述,有兴趣的可以直接看源码:enhanced-axios

本解决方案只是针对后端返回的各式各样的数据格式进行规范化,其他的比如 Token 注入与刷新,全局加载,判断跳转登录等功能并未包含,可在该方案的基础上拓展。

另外,由于已经写在了 github 上了,就不再贴代码了,直接上链接,有需要直接点击链接查看。

拦截器一:处理后端接口的封装参数的一致性

这里主要是对后端业务参数的转换,比如 message/msg, code/flag, data/result 后端接口可能出现的业务参数转成标准的 message(响应信息)/code(业务代码)/data(业务数据),这里我把最后转换的数据放在属性 _business 里面,流转到下一个拦截器。

注意,在这个(或者后续的)拦截器里如果是报错信息同样也需要通过 Promise.reject(error) 的方式流转到下一个拦截器。

点击处理后端接口的封装参数的一致性查看代码。

拦截器二:处理401响应

在我的经验里,在用户未登录或登录过期时接口一般会有以下两种情况:

  • HTTP 状态码 401
  • HTTP 状态码 200/500,但业务数据未认证

这里我需要把第二种情况包装成第一种情况,使得在后续的业务中能够统一判断是否需要去登录。

在代码里借鉴了 Axios 自身的 createError 方式来实现把其他的响应数据转成 401 的响应数据。

点击处理401响应查看代码

拦截器三:处理后端接口的多重封装

这一步主要是解包多重的数据,直接在第一个拦截器返回的 _business 里面读取即可,如果不是成功的响应,则封装成一个 500 的错误响应(同样使用和上述包装 401 响应的方式)

这里也处理了接口报错的信息,可通过配置来弹出或忽略信息提示。

点击处理后端接口的多重封装查看代码

拦截器四:处理后端接口的响应错误

最后的拦截器来处理最终的错误信息,上述的拦截器,只要报错了都会通过 Promise.reject(error) 流转到这里

点击处理后端接口的响应错误查看代码

使用方式

具体的类型定义及说明可点击这里查看

import {
  enhancedAxios
} from 'enhanced-axios'
import type { EAxiosInstance } from 'enhanced-axios'

const businessService = axios.create({}) as EAxiosInstance

enhancedAxios(
  // axios 实例
  businessService,
  {
    // 成功的业务代码
    validBusinessCodes: [200],
    // 用户未认证的业务代码
    unanthorizedBusinessCodes: [10011039, 10011040, 10011041],
    // 业务数据与业务代码的别名, 默认是 code message data
    businessAlias: { data: 'result' },
    // 执行失败信息的函数,一般是UI组件的函数
    warning: (message: string) => notice('danger', message),
    // 执行成功信息的函数,一般是UI组件的函数
    success: (message: string) => notice('success', message),
    // 配置响应成功是否弹出信息
    _feedback: false,
    // 额外的拦截器配置
    interceptors: {
      request: [],
      response: [],
    },
  }
)

businessService<MyBusinessDataType>({
  url: 'api-url',
  method: 'get',
  // 如上
  _feedback: '成功了',
  // 禁止弹出接口返回的 message 及配置的 _feedback
  _silent: true
}).then((businessData) => {
  // 获取业务数据,类型:MyBusinessDataType
  console.log(businessData)
}, (error) => {
  // 捕获 http 错误或业务错误
  console.log(error)
  // 打印由插件默认生成的错误信息
  console.log(error._formatMessage())
})

// 如果你的某些请求,想要原始的响应对象,那么创建多一个 axios 实例来请求即可
const originService = axios.create({}) // AxiosInstance

总结

最后,感谢阅读,如果文章有什么错误的地方可以指出,如果文章对您有帮助可以点个赞,给个 star, 再贴一下源码:enhanced-axios,有兴趣的可以直接看。