原文地址:monine.github.io/#/article/2…
最近工作贼忙,这篇文章按说应该两个月之前就产出,可是每天的精力基本都用在工作上,一写文章就犯迷糊,断断续续的每次要重新屡逻辑,以后再也不这样了。这篇文章是我司后台项目中遇到的一个基础需求,自己设计了一个实现方案,感觉还不错。
需求
后端接口响应,根据与后端约定的状态码(非 http 状态码)判定接口是否异常,我司的约定是 status !== 0
则表示接口异常。一旦接口处于异常状态,先让业务端(调用者)处理异常,再由业务端决定是否执行接口异常统一处理(目前我司的统一处理内容就是弹出个 element-ui message 提示消息 ?)。
这个流程有一个难点,当接口响应后处于异常状态,先交由业务端处理,再由业务端决定是否执行统一处理?
API 层我司使用的是第三方库 axios,接口响应后会先走响应拦截器,再走业务端代码。 正常的接口异常统一处理流程,是在响应拦截器内判定,与后端约定的响应状态码是否为异常状态码。如果是,则先执行统一处理逻辑,再交由到业务端处理。那现在的需求是将接口异常处理的流程逆转,接口响应状态异常之后,先交由业务端执行异常处理,再由业务端决定是否执行接口异常状态统一处理。
如上所说,如果接口处于异常状态,需要判定是否要执行统一处理,分两种情况:
- 业务端没有处理异常,必然要执行统一处理。
- 业务端已经处理异常,并且主动声明是否继续执行统一处理。(主动声明该如何设计?)
问题来了,接口异常统一处理的代码应该写在哪里?如何保证它在状态异常情况下,先交由业务端处理,再根据业务端的声明判定是否执行统一处理。
历史解决方案之 mixin
我司之前已经有过处理方案,不过是只针对 vue 框架下的处理,通过 mixin 将 methods 内所有方法进行覆写,补丁函数内对源函数执行完成之后获取的返回结果进行判定,如果返回结果为 Promise 类型,则继续进行相关异常处理操作。这样确实能够达到实现效果,但总觉得很不优雅:
- API 层的处理与框架深度绑定,这本身就不合理。
- methods 内函数全部被覆写,大量无用开销;如果进行函数名称约定,加入覆写筛选,这又增加约定成本。
- 只能应用于 vue 框架,无法再其它项目下直接使用,很局限。
当时我了解到上述的处理方案后第一反应是 API 层的任何操作都不应该与框架本身进行任何关联绑定,如同当年 vue 从全家桶中移除 vue-resource 一样。
我的解决方案
经过一些思考,大致确定了一个思路:利用 Promise 状态的稳定,以接口名称作为唯一标识,表示当前接口是否还需要执行统一处理。。
我是这样设计的,接口调用时使用 url 作为唯一标识,以状态的形式保存在数组内 const unhandleAPI = []
。
接口返回后进入响应拦截器,在此对接口响应状态进行判断,如果属于异常状态,则使用 setTimeout
将接口异常统一处理函数设置为 macro task,作为一个异步任务推迟到下一轮 Event Loop 再执行,并返回 Promise.reject
。然后会进入业务端接口调用代码的 catch 回调函数内,执行完业务异常处理后,如果没有返回值,则表示无需再执行统一处理。相反,返回非 undefined
值,则表示还需要执行统一处理。
执行接口异常统一处理之前,先判定 url 标识是否存在于 unhandleAPI
内,如存在,则执行统一处理。
以上是一个大致的设计思路,具体到实现还需要解决一些实际问题:
- 如何确定接口的唯一性?因为同一个接口可能毫秒内被多次调用。
- 接口的异常状态以什么样的形式进行保存?
- 如何在适当的时候移除接口异常状态?比如业务端处理了异常不想再执行统一处理。
具体实现
-
接口异常处理状态存储
使用一个数组对象
const unhandleAPI = []
保存所有已经调用但暂未响应的接口唯一标识,接口异常统一处理函以此判定判定是否需要执行。另外对外暴露一些操作unhandleAPI</