背景
现在有个需求,要求封装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)
})
}
关键点
- 只有调用了then才能判断then方法中有无onRejected回调函数。
- then中无论执行onFullfilled或是onRejected函数,若没有抛异常,return的值作为下一个then中onFullfilled函数的参数,若抛异常,throw的值作为下一个then中onRejected函数的参数。
- Promise之后的then方法是立即执行的,放入微任务队列延迟执行的是传入then方法的onFullfilled和onRejected函数。
- 同理在then方法执行完毕后,catch方法也是立即执行的,放入微任务队列延迟执行的是传入catch方法的回调函数。
- 重写这个Promise实例的then和catch方法,一定要return,传参给后续的then使用。