前言
了解过在实际项目中处理 async await 异常方式同学应该知道,常见的捕获方式:
- 使用
errorCaptured来调用async函数。 - 使用
plugin或loader在打包的时候统一包裹try catch。
相比较这两者,显然后者更好,因为它甚至连 errorCaptured 都不需要写。但是,实际中,使用 loader 并不是这么简单,例如当你的项目是基于「Vue CLI」的时候,此时你想改一些配置,那可能够你喝一壶的...
所以,今天我们就来聊聊如何优雅地处理基于「Vue CLI」项目中的 async await 异常~
准备一个 Loader
其实,对于「loader」而言,这里可以是「babel-plugin」,也可以是「webpack-loader」。前者是基于 visitor,后者是基于 @babel/traverse 的能力。但是,这两者的本质是一样的,都是针对 AwaitExpression AST 节点来做 TryStatement 「AST」节点的添加。
这里我们就用很多同学熟悉的桃翁写的一个「webpack-loader」async-catch-loader:
- 安装
async-catch-loader依赖:
npm i async-catch-loader -D
2.优化一下 async-catch-loader 源码:
// 部分代码
...
traverse(ast, {
AwaitExpression(path) {
// 递归向上找异步函数的 node 节点
while (path && path.node) {
let parentPath = path.parentPath;
if (
// 找到 async Function
t.isBlockStatement(path.node) &&
isAsyncFuncNode(parentPath.node)
) {
let tryCatchAst = t.tryStatement(
path.node,
t.catchClause(
t.identifier(options.identifier),
t.blockStatement(catchNode)
),
finallyNode && t.blockStatement(finallyNode)
);
path.replaceWithMultiple([tryCatchAst]);
return;
} else if (
// 已经包含 try 语句则直接退出
t.isBlockStatement(path.node) &&
t.isTryStatement(parentPath.node)
) {
return;
}
path = parentPath;
}
}
});
...
可以看到,原先它是在 traverse 的 AwaitExpression 中通过循环 path 和 path = path.parent 的方式向上查找 BlockStatement,即块级 AST 节点,然后替换该 AST 节点为一个 TryStatement AST 节点。
但是,其实 babel 的 path 对象上提供了很多便捷的方法可以便于节点的查找,这里我们使用 findParent() 来代替 while 循环向上查找过程:
traverse(ast, {
AwaitExpression(path) {
// 已经包含 try 语句则直接退出
if (
path.findParent(path => t.isTryStatement(path.node))
) {
return;
}
const blockParent = path.findParent(path => t.isBlockStatement(path.node))
const tryCatchAst = t.tryStatement(
blockParent.node,
t.catchClause(
t.identifier(options.identifier),
t.blockStatement(catchNode)
),
finallyNode && t.blockStatement(finallyNode)
)
blockParent.replaceWithMultiple([tryCatchAst])
}
});
在处理好 async-catch-loader 后,我们就需要使用它,即在「Webpack」对应规则中配置它。
魔改 Vue CLI 源码
魔改一词,听起来是不是有点激动。但是,实不相瞒,我们只是在「Vue CLI」的源码中加了几行代码。
❝当然,这也是在理解「Vue CLI」的运行流程的前提下,才能这么精准的加代码!
❞
由于,我们需要借助 @babel/traverse 的能力,所以必须要在 babel-loader 对代码进行转化之前使用它。
而,传统的基于「Vue CLI」使用 JavaScript 开发的同学倒是不用担忧这个问题,直接使用官网提供的 API 在 js 规则下添加 async-catch-loader 就行:
const jsRule = config.module.rule('js')
jsRule.use('async-catch-loader')
.loader('async-catch-loader')
.tap(() => {
return {
catchCode: `console.error(e)`
}
})
但是,对于基于「Vue CLI」使用 TypeScript 开发的同学,可能就有点伤脑筋了...因为,此时在 babel-loader 之前加载的是 ts-loader,如果此时你也像上面一样直接添加 async-catch-loader,那迎接你的就是一堆报错,告诉你一些 feature 暂不支持。
并且,很不幸地是「Vue CLI」并不支持在某一个地方插入一个 loader,你只能自上而下地添加 loader。那么,此时可供我们选择的只有重写整个 ts 规则对应的 loader,但是实际上你只是为了加一个 loader,其余 loader 只是还原原先写法。明显,这个选择很...
所谓,山穷水尽疑无路,柳暗花明又一村。「Vue CLI」中是由 @vue/cli-plugin-typescript/index.js 来负责处理 .ts 文件,进行 .ts 到 .js 的转化过程。所以,如果仅仅是本地环境下,我们可以在这个文件中添加 async-catch-loader,让它在 ts-loader 之后加载,babel-loader 之前加载:
// node_modules/@vue/cli-plugin-typescript/index.js 64 行
addLoader({
name: "async-catch-loader",
loader: require.resolve("async-catch-loader"),
options: {
catchCode: `console.error(e)`
}
})
这样,最终 .ts 规则的 loader 加载顺序就是 ts-laoder 到 async-catch-loader 再到 babel-loader,也就避免了重写 .ts 规则时的重复劳动,便捷地实现了 async-catch-loader 的引入。
写在最后
虽然,通过往 @vue/cli-plugin-typescript/index.js 手动加入代码可以更改 loader 加载顺序,然后实现优雅地处理 async await 异常。但是,这种方式存在的缺陷是很明显的,由于我们是在安装的 npm 包中修改的代码,那么在团队合作方面显然是不友好的,因为每个人都会存在第一次或重新安装依赖的过程,此时如果他们也需要打包,就需要重复上述操作。
❝所以,这之间的取舍,各位同学自己看哈,或者其他方式实现。
❞
参考
往期文章回顾
深度解读 Vue3 源码 | 组件创建过程 | 掘金技术征文-双节特别篇
深度解读 Vue3 源码 | 内置组件 teleport 是什么“来头”?
深度解读 Vue3 源码 | compile 和 runtime 结合的 patch 过程
❤️爱心三连击
写作不易,如果你觉得有收获的话,可以爱心三连击!!!
❝前端问路人 —— 五柳(「微信公众号: Code center」)
❞