背景

现在有个需求,要求封装axios,实现全局异常处理和个性异常处理,若有个性异常处理覆盖全局异常处理。
假如约定正常返回码为0000,后端不为0000的异常返回,前端全局异常处理为toast这个异常。这个使用axios的response拦截器很容易实现。

request.interceptors.response.use(response => {
   
  if(response.data.retCode != '0000') {
   
    toast(response.data.errMsg)
    return Promise.reject(errMsg)
  } else {
   
    return response.data
  }
})

但如果某个请求设置了自己的个性化异常处理,不想toast这个异常呢?现在需要实现这样的功能。

// 没有异常处理,使用全局的toast弹出异常信息
request('/system/user', {
   userId: 1}).then(res => {
   
  // 业务处理
})
// 有异常处理,不弹出toast,使用自己的异常处理
request('/system/user', {
   userId: 1}).then(res => {
   
  // 业务处理
}, err => {
   
  console.log(err)
})

axios这个拦截器是必定执行的,似乎使用拦截器无法实现。

实现方案

这里的实现方案主要是重写Promise的then方法和catch方法
首先使用setTimeout模拟一个异步ajax请求,将其封装成Promise,这里使用两个参数,若有第二个参数,则request失败,执行reject(reason)

function request (value, reason) {
   
  return new Promise((resolve, reject) => {
   
    setTimeout(() => {
   
      reason ? reject(reason) : resolve(value)
    }, 1000)
  })
}

重写then方法,支持个性化异常处理

function ajaxFail (reason) {
   
  return ajax(null, reason)
}
function ajax (value, reason) {
   
  const ajaxPromise = request(value, reason).then(res => {
   
    return res
  }, err => {
   
    throw err
  })
  const oldThen = ajaxPromise.then
  ajaxPromise.then = (onFulfilled, onRejected) => {
   
    const afterThen = oldThen.call(ajaxPromise, res => {
   
      return onFulfilled(res)
    }, err => {
   
      if (onRejected) {
   
        return onRejected(err)
      } else {
   
        console.error('默认异常reject处理:' + err)
        return err
      }
    })
  }
  return ajaxPromise
}

测试1

function testF1 () {
   
  console.log('失败,有then(有onRejected)')
  ajaxFail('fail1').then(res => {
   
    console.log('res', res)
  }, err => {
   
    console.log('err', err)
  })
}
失败,有then(有onRejected)
err fail1

测试2

function testF2 () {
   
  console.log('失败,then(无onRejected)')
  ajaxFail('fail1').then(res => {
   
    console.log('res', res)
  })
}
失败,then(无onRejected)
默认异常reject处理:fail1

测试3

function testF3 () {
   
  console.log('失败,无then')
  ajaxFail('fail1')
}
失败,无then
(node:36084) UnhandledPromiseRejectionWarning: fail1

可以看到前2个测试结果满足预期,但第3个测试当请求没写then方法时,会报错,需要支持没有then的情况

判断请求后面有无then方法

当请求后面没有then方法时,使用全局异常处理。
继续完善ajax请求函数,增加判断请求后面有无then方法的逻辑。

function ajax (value, reason) {
   
  const ajaxPromise = request(value, reason).then(res => {
   
    return res
  }, err => {
   
    if (!ajaxPromise.hasThen) {
   
      console.error('默认异常处理:' + err)
    } else {
   
      throw reason
    }
  })
  ajaxPromise.hasThen = false
  const oldThen = ajaxPromise.then
  ajaxPromise.then = (onFulfilled, onRejected) => {
   
    ajaxPromise.hasThen = true
    const afterThen = oldThen.call(ajaxPromise, res => {
   
      return onFulfilled(res)
    }, err => {
   
      if (onRejected) {
   
        return onRejected(err)
      } else {
   
        console.error('默认异常reject处理:' + err)
        return err
      }
    })
  }
  return ajaxPromise
}

测试3

function testF3 () {
   
  console.log('失败,无then')
  ajaxFail('fail1')
}
失败,无then
默认异常处理:fail1

测试通过,这里的关键点就是重写then时将ajaxPromise.hasThen置为true,在request的then方法中的onRejected回调函数中判断ajaxPromise.hasThen是否为true,若不为true,走默认异常处理逻辑

增加catch的支持

promise中的异常处理一般支持两种写法,then中的第二个回调onRejected或者是catch中的回调函数,这里可以重写catch,用来支持这两种个性化的异常处理。
继续补充ajax函数,重写promise的catch方法

function ajax (value, reason) {
   
  const ajaxPromise = new Promise((resolve, reject) => {
   
    request(value, reason).then(res => {
   
      resolve(value)
    }, err => {
   
      if (!ajaxPromise.hasThen) {
   
        console.error('默认异常处理:' + err)
      } else {
   
        reject(reason)
      }
    })
  })
  ajaxPromise.hasThen = false
  ajaxPromise.hasCatch = false
  const oldThen = ajaxPromise.then
  ajaxPromise.then = (onFulfilled, onRejected) => {
   
    ajaxPromise.hasThen = true
    const afterThen = oldThen.call(ajaxPromise, res => {
   
      return onFulfilled(res)
    }, err => {
   
      if (ajaxPromise.hasCatch) {
   
        throw err
      }
      if (onRejected) {
   
        return onRejected(err)
      } else {
   
        console.error('默认异常reject处理:' + err)
        return err
      }
    })
    const oldCatch = afterThen.catch
    afterThen.catch = onRejected => {
   
      ajaxPromise.hasCatch = true
      return oldCatch.call(afterThen, err => {
   
        return onRejected(err)
      })
    }
    return afterThen
  }
  return ajaxPromise
}

测试4

function testF4 () {
   
  console.log('失败,then(无onRejected)接catch')
  ajaxFail('fail1').then(res => {
   
    console.log('res', res)
  }).catch(err => {
   
    console.log('err', err)
  })
}
失败,then(无onRejected)接catch
err fail1

测试通过,这里的关键点

  • 重写catch和重写then的逻辑基本一样,但catch方法里只有一个回调函数
  • ajaxPromise.then的第二个回调函数onRejected中需要判断是否有catch方法,若有,抛出异常,将参数传给下一个catch方法中的回调函数

完整代码

function ajaxFail(reason) {
   
  return ajax(null, reason)
}

function ajax (value, reason) {
   
  const ajaxPromise = new Promise((resolve, reject) => {
   
    request(value, reason).then(res => {
   
      resolve(value)
    }, err => {
   
      if (!ajaxPromise.hasThen) {
   
        console.error('默认异常处理:' + err)
      } else {
   
        reject(reason)
      }
    })
  })
  ajaxPromise.hasThen = false
  ajaxPromise.hasCatch = false
  const oldThen = ajaxPromise.then
  ajaxPromise.then = (onFulfilled, onRejected) => {
   
    ajaxPromise.hasThen = true
    // then 当中的值 return 可以作为下个 then 的参数
    const afterThen = oldThen.call(ajaxPromise, res => {
   
      return onFulfilled(res)
    }, err => {
   
      if (ajaxPromise.hasCatch) {
   
        throw err
      }
      if (onRejected) {
   
        return onRejected(err)
      } else {
   
        console.error('默认异常reject处理:' + err)
        return err
      }
    })
    const oldCatch = afterThen.catch
    afterThen.catch = onRejected => {
   
      ajaxPromise.hasCatch = true
      return oldCatch.call(afterThen, err => {
   
        return onRejected(err)
      })
    }
    return afterThen
  }
  return ajaxPromise
}
// 模拟异步请求
function request (value, reason) {
   
  return new Promise((resolve, reject) => {
   
    setTimeout(() => {
   
      reason ? reject(reason) : resolve(value)
    }, 1000)
  })
}

关键点

  1. 只有调用了then才能判断then方法中有无onRejected回调函数。
  2. then中无论执行onFullfilled或是onRejected函数,若没有抛异常,return的值作为下一个then中onFullfilled函数的参数,若抛异常,throw的值作为下一个then中onRejected函数的参数。
  3. Promise之后的then方法是立即执行的,放入微任务队列延迟执行的是传入then方法的onFullfilled和onRejected函数。
  4. 同理在then方法执行完毕后,catch方法也是立即执行的,放入微任务队列延迟执行的是传入catch方法的回调函数。
  5. 重写这个Promise实例的then和catch方法,一定要return,传参给后续的then使用。