前言
为了统一错误处理的规范,将常见的错误处理封装到一个 hook 里面方便在业务代码中调用。
本次封装只会考虑两种常见错误:
- JavaScript 语法异常及运行时错误
- 异步请求异常
代码运行时错误处理
运行时错误处理提供两种处理方式以应对不同的使用场景:
- 辅助函数返回一个新的函数供用户自行调用
- 辅助函数直接调用传入的函数
bind 方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
export function useErrHelper() {
const bindWithErr = (fn) => {
return (...args) => {
try {
return fn && fn(...args)
} catch (err) {
console.error('捕获到了错误: ', err)
}
}
}
return {
bindWithErr
}
}
|
bind 用法
这个辅助函数会将用户传入的参数包裹到 try catch
中执行,并返回一个新的函数,让用户自行调用。
适合用于注册未页面 handler 的回调函数。
用一个自己的业务场景举例:一个下拉框组件的回调参数可能会包含一个字符串数学表达式,而我们需要将这个表达式执行,并避免执行错误时程序崩溃:
1
2
|
<!-- 例,伪代码 -->
<el-select @change="setCamera"></el-select>
|
1
2
3
4
5
6
7
8
9
10
11
|
/**
* 设置相机参数
* @param {* Object} val 相机参数对象
*/
const setCamera = bindWithErr((val) => {
if (!val) return
cameraCondition.value = {
cmos: eval(val.cmos),
shutterSpeed: eval(val.shutterSpeed),
}
})
|
call 方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
export function useErrHelper() {
const callWithErr = (fn, ...args) => {
try {
return fn && fn(...args)
} catch (err) {
console.error('捕获到了错误: ', err)
}
}
return {
callWithErr
}
}
|
call 用法
就是直接调用用户传入的参数,比较简单粗暴,不展开解析。
异步错误处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
export function useErrHelper() {
/**
* 异步调用错误捕获
* @param {* Function} promise 异步函数,一般是接口调用
* @param {* String} successMsg 成功时的提示信息,如果不需要提示,就不要传
* @param {* String} failMsg 失败时的提示信息,如果不需要提示或者需要使用后端返回的信息,就不要传(建议不传)
* @returns 返回 null 或者 promise 的结果
*/
const awaitWithErr = async (promise, options = { successMsg: '', failMsg: '' }) => {
const [err, res] = await promise.then((data) => [null, data]).catch((err) => [err, null])
if (err || res.data.code >= 500) {
let msg
if (options.failMsg) {
msg = options.failMsg
} else {
msg = err ? err.message : res.data.msg
}
ElMessage({ showClose: false, message: msg, type: 'error' })
return null
} else if (res.data.code == 200) {
if (options.successMsg) ElMessage({ showClose: false, message: options.successMsg, type: 'success' })
return res
}
}
return {
awaitWithErr
}
}
|
用法
-
这个辅助函数将 ElementUI 的提示函数封装在里面,减少了业务组件中不必要的代码,并且会根据是否传入成功或错误信息,判断是否需要进行提示。
-
500
和200
是后端定义的业务状态码,可以根据实际业务代码进行替换,或直接抽取到 options 中作为参数传入。
-
注意该辅助函数的返回结果为 null
或者 promise 异步数据
,也就是说在业务组件中,只需要判断返回值是否为空即可。
1
2
3
4
5
|
// 不需要任何提示的接口调用,如列表等分页接口
const res = await awaitWithErr(queryList(keyWord))
if (!res) {
// 异步错误时 do something ...
}
|
1
2
3
4
5
|
// 需要提示的接口调用,如编辑等操作
const res = await awaitWithErr(deleteById(id), { successMsg: '删除成功' })
if (res) {
// 操作成功时 do something ...
}
|
allSettled 并发版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// 并发异步调用辅助函数(allSettled 版本)
const allSettledWithErr = async (promises, options = { successMsg: '', failMsg: '' }) => {
const results = await Promise.allSettled(promises)
const ress = results.filter((result) => result.status === 'fulfilled')
const result = results.map((result) => {
if (result.status === 'fulfilled') {
return result.value
} else {
console.error(result.reason, '异步调用错误信息')
return null
}
})
if (ress.length) {
if (options.successMsg) ElMessage({ showClose: false, message: options.successMsg, type: 'success' })
return result
} else {
if (options.failMsg) {
ElMessage({ showClose: false, message: options.failMsg, type: 'error' })
}
return null
}
}
|
用法
并发版本用法跟普通版本一致,简化了并发请求时的冗余处理。
(完)