axios的兜底错误处理

2,651 阅读3分钟

项目的网络请求层使用了axios,axios是一个及其好用的库,但是在业务使用上出现了一些问题

出现原因

众所周知,前端一般通过ajax向后端请求数据然后处理后显示在网页/app上,当然人都是懒得,总是希望有一些自动完成的事,比如这一次,如果不是需要特殊处理的特定错误,直接弹toast结束。

错误处理还是很容易,axios增加拦截器,如果是指定的错误,直接throw new Error('错误') 剩下的交给promise.catch干就好了,问题集中在如何兜底这个catch,如果不处理,会变成unhandledrejection错误

解决方案

最原始的方案

高阶函数 throwMessage,自动在promise之后增加catch函数,问题是这个要记得手动引入,和自动这个理念还是有点区别的.


export function throwMessage<T = any>(responsePromise: Promise<T>) {
  return responsePromise.catch((e: Error) => {
    console.error(`error`, e)
    message.error(e.message)
  })
}

每次使用都要throwMessage(promise)容易忘,如果有必要,还要写lint规则防止忘记

webpack方案

既然打包的是webpack,那么使用webpack在转译原始代码的时候注入就好了嘛. 推荐一个promise-catch-loader自动处理好这一系列问题 具体因为没有太研究,不做展开

事件监听方案

上文提到了,如果promise没有捕获错误,会自动变成unhandlered rejection错误,这个是可以全局监听的,具体在MDN上可以看

那么 我们在window上直接监听就好了

window.addEventListener('unhandledrejection', event => {
  event.preventDefault()
});

问题是,太全局了

处理Promise链方案

这种是最终处理的方案,当然也是最魔改的方案,考虑到只运行在前端,内存溢出情况不多,方能实现

axios返回的是promise,promise是遵循PromiseA+方案,内部其实是有一条promise链的,我们的目的就在于,在promise链条的最后一个promise上挂上catch,反正预先被处理了最后一个也不会触发

问题来了

如果你直接看promise的话,是只能看到,then,catch,finally这些方法的,并没办法看到promise链,也没办法顺着链访问到最后

如何解决

我们的目标是将其变为链式结构,简单来说,只要每个then和catch都能记下他的后继即可。
所以最终我选择注入promise的两个方法

实现代码

/**
 * 继承然后注入两个方法
 */
export class ChainedPromise<T> extends Promise<T> {
  nextPromise?: Promise<any>

  then<TResult1 = T, TResult2 = never>(
    onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
    onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
  ): Promise<TResult1 | TResult2> {
    const promise = super.then(onfulfilled, onrejected)
    this.nextPromise = promise
    return promise
  }
  catch<TResult = never>(
    onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null,
  ): Promise<T | TResult> {
    const promise = super.catch(onrejected)
    this.nextPromise = promise
    return promise
  }
}

/**
 * 暴露给外面的方法,axios通过拦截器,通过封装service都能调用它来在入口处自动注入
 */
function autoTravel<T>(promise: Promise<T>): Promise<T> {
  const getLastPromise = function(promise: ChainedPromise<any>): Promise<any> {
    if (promise.nextPromise) {
      return getLastPromise(promise.nextPromise)
    }
    return promise
  }

  /**
   * Promise内部的自动形成链条,就能顺着链条找到最后一个了
   */
  const autoTravelPromise = new ChainedPromise<T>((resolve, reject) => {
    promise.then(resolve).catch(responseError => {
  	  // throwMessage为之前定义的,promise上自动加catch的方法
      throwMessage(getLastPromise(autoTravelPromise))
      reject(responseError)
    })
  })
  return autoTravelPromise
}