怎么给所有的async函数添加try/catch


这篇“怎么给所有的async函数添加try/catch”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么给所有的async函数添加try/catch”文章吧。

//示例
asyncfunctionfn(){
letvalue=awaitnewPromise((resolve,reject)=>{
reject('failure');
});
console.log('dosomething...');
}
fn()

导致浏览器报错:一个未捕获的错误在开发过程中,为了保证系统健壮性,或者是为了捕获异步的错误,需要频繁的在 async 函数中添加 try/catch,避免出现上述示例的情况可是我很懒,不想一个个加,懒惰使我们进步?下面,通过手写一个babel 插件,来给所有的async函数添加try/catch原始代码:

asyncfunctionfn(){
awaitnewPromise((resolve,reject)=>reject('报错'));
awaitnewPromise((resolve)=>resolve(1));
console.log('dosomething...');
}
fn();

使用插件转化后的代码:

asyncfunctionfn(){
try{
awaitnewPromise((resolve,reject)=>reject('报错'));
awaitnewPromise(resolve=>resolve(1));
console.log('dosomething...');
}catch(e){
console.log("nfilePath:E:myappsrcmain.jsnfuncName:fnnError:",e);
}
}
fn();

打印的报错信息:通过详细的报错信息,帮助我们快速找到目标文件和具体的报错方法,方便去定位问题1)借助AST抽象语法树,遍历查找代码中的await关键字2)找到await节点后,从父路径中查找声明的async函数,获取该函数的body(函数中包含的代码)3)创建try/catch语句,将原来async的body放入其中4)最后将async的body替换成创建的try/catch语句先聊聊 AST 这个帅小伙?,不然后面的开发流程走不下去AST是代码的树形结构,生成 AST 分为两个阶段:词法分析语法分析词法分析词法分析阶段把字符串形式的代码转换为令牌(tokens) ,可以把tokens看作是一个扁平的语法片段数组,描述了代码片段在整个代码中的位置和记录当前值的一些信息比如let a = 1,对应的AST是这样的语法分析语法分析阶段会把token转换成 AST 的形式,这个阶段会使用token中的信息把它们转换成一个 AST 的表述结构,使用type属性记录当前的类型例如 let 代表着一个变量声明的关键字,所以它的 type 为 VariableDeclaration,而 a = 1 会作为 let 的声明描述,它的 type 为 VariableDeclaratorAST在线查看工具AST explorer再举个?,加深对AST的理解

functiondemo(n){
returnn*n;
}

转化成AST的结构

{
"type":"Program",//整段代码的主体
"body":[
{
"type":"FunctionDeclaration",//function的类型叫函数声明;
"id":{//id为函数声明的id
"type":"Identifier",//标识符类型
"name":"demo"//标识符具有名字
},
"expression":false,
"generator":false,
"async":false,//代表是否是asyncfunction
"params":[//同级函数的参数
{
"type":"Identifier",//参数类型也是Identifier
"name":"n"
}
],
"body":{//函数体内容整个格式呈现一种树的格式
"type":"BlockStatement",//整个函数体内容为一个块状代码块类型
"body":[
{
"type":"ReturnStatement",//return类型
"argument":{
"type":"BinaryExpression",//BinaryExpression二进制表达式类型
"start":30,
"end":35,
"left":{//分左右中结构
"type":"Identifier",
"name":"n"
},
"operator":"*",//属于操作符
"right":{
"type":"Identifier",
"name":"n"
}
}
}
]
}
}
],
"sourceType":"module"
}

1)原始代码

asyncfunctionfn(){
awaitf()
}

对应的AST结构2)增加try catch后的代码

asyncfunctionfn(){
try{
awaitf()
}catch(e){
console.log(e)
}
}

对应的AST结构

对应的AST结构
通过AST结构对比,插件的核心就是将原始函数的body放到try语句中插件的基本格式示例

module.exports=function(babel){
lett=babel.type
return{
visitor:{
//设置需要范围的节点类型
CallExression:(path,state)=>{
dosoming……
}
}
}
}

1)通过 babel 拿到 types 对象,操作 AST 节点,比如创建、校验、转变等2)visitor:定义了一个访问者,可以设置需要访问的节点类型,当访问到目标节点后,做相应的处理来实现插件的功能回到业务需求,现在需要找到await节点,可以通过AwaitExpression表达式获取

module.export免费云主机域名s=function(babel){
lett=babel.type
return{
visitor:{
//设置AwaitExpression
AwaitExpression(path){
//获取当前的await节点
letnode=path.node;
}
}
}
}

通过findParent方法,在父节点中搜寻 async 节点

//async节点的属性为true
constasyncPath=path.findParent(p=>p.node.async)

async 节点的AST结构这里要注意,async 函数分为4种情况:函数声明 、箭头函数 、函数表达式 、函数为对象的方法

//1️⃣:函数声明
asyncfunctionfn(){
awaitf()
}

//2️⃣:函数表达式
constfn=asyncfunction(){
awaitf()
};

//3️⃣:箭头函数
constfn=async()=>{
awaitf()
};

//4️⃣:async函数定义在对象中
constobj={
asyncfn(){
awaitf()
}
}

需要对这几种情况进行分别判断

module.exports=function(babel){
lett=babel.type
return{
visitor:{
//设置AwaitExpression
AwaitExpression(path){
//获取当前的await节点
letnode=path.node;
//查找async函数的节点
constasyncPath=path.findParent((p)=>p.node.async&&(p.isFunctionDeclaration()||p.isArrowFunctionExpression()||p.isFunctionExpression()||p.isObjectMethod()));
}
}
}
}

babel-template可以用以字符串形式的代码来构建AST树节点,快速优雅开发插件

//引入babel-template
consttemplate=require('babel-template');

//定义try/catch语句模板
lettryTemplate=`
try{
}catch(e){
console.log(CatchError:e)
}`;

//创建模板
consttemp=template(tryTemplate);

//给模版增加key,添加console.log打印信息
lettempArgumentObj={
//通过types.stringLiteral创建字符串字面量
CatchError:types.stringLiteral('Error')
};

//通过temp创建try语句的AST节点
lettryNode=temp(tempArgumentObj);

module.exports=function(babel){
lett=babel.type
return{
visitor:{
AwaitExpression(path){
letnode=path.node;
constasyncPath=path.findParent((p)=>p.node.async&&(p.isFunctionDeclaration()||p.isArrowFunctionExpression()||p.isFunctionExpression()||p.isObjectMethod()));

lettryNode=temp(tempArgumentObj);

//获取父节点的函数体body
letinfo=asyncPath.node.body;

//将函数体放到try语句的body中
tryNode.block.body.push(...info.body);

//将父节点的body替换成新创建的try语句
info.body=[tryNode];
}
}
}
}

到这里,插件的基本结构已经成型,但还有点问题,如果函数已存在try/catch,该怎么处理判断呢?

//示例代码,不再添加try/catch
asyncfunctionfn(){
try{
awaitf()
}catch(e){
console.log(e)
}
}

通过isTryStatement判断是否已存在try语句

module.exports=function(babel){
lett=babel.type
return{
visitor:{
AwaitExpression(path){

//判断父路径中是否已存在try语句,若存在直接返回
if(path.findParent((p)=>p.isTryStatement())){
returnfalse;
}

letnode=path.node;
constasyncPath=path.findParent((p)=>p.node.async&&(p.isFunctionDeclaration()||p.isArrowFunctionExpression()||p.isFunctionExpression()||p.isObjectMethod()));
lettryNode=temp(tempArgumentObj);
letinfo=asyncPath.node.body;
tryNode.block.body.push(...info.body);
info.body=[tryNode];
}
}
}
}

获取报错时的文件路径 filePath 和方法名称 funcName,方便快速定位问题获取文件路径

//获取编译目标文件的路径,如:E:myappsrcApp.vue
constfilePath=this.filename||this.file.opts.filename||'unknown';

获取报错的方法名称

//定义方法名
letasyncName='';

//获取async节点的type类型
lettype=asyncPath.node.type;

switch(type){
//1️⃣函数表达式
//情况1:普通函数,如constfunc=asyncfunction(){}
//情况2:箭头函数,如constfunc=async()=>{}
case'FunctionExpression':
case'ArrowFunctionExpression':
//使用path.getSibling(index)来获得同级的id路径
letidentifier=asyncPath.getSibling('id');
//获取func方法名
asyncName=identifier&&identifier.node?identifier.node.name:'';
break;

//2️⃣函数声明,如asyncfunctionfn2(){}
case'FunctionDeclaration':
asyncName=(asyncPath.node.id&&asyncPath.node.id.name)||'';
break;

//3️⃣async函数作为对象的方法,如vue项目中,在methods中定义的方法:methods:{asyncfunc(){}}
case'ObjectMethod':
asyncName=asyncPath.node.key.name||'';
break;
}

//若asyncName不存在,通过argument.callee获取当前执行函数的name
letfuncName=asyncName||(node.argument.callee&&node.argument.callee.name)||'';

用户引入插件时,可以设置excludeincludecustomLog选项exclude: 设置需要排除的文件,不对该文件进行处理include: 设置需要处理的文件,只对该文件进行处理customLog: 用户自定义的打印信息入口文件index.js

//babel-template用于将字符串形式的代码来构建AST树节点
consttemplate=require('babel-template');

const{tryTemplate,catchConsole,mergeOptions,matchesFile}=require('./util');

module.exports=function(babel){
//通过babel拿到types对象,操作AST节点,比如创建、校验、转变等
lettypes=babel.types;

//visitor:插件核心对象,定义了插件的工作流程,属于访问者模式
constvisitor={
AwaitExpression(path){
//通过this.opts获取用户的配置
if(this.opts&&!typeofthis.opts==='object'){
returnconsole.error('[babel-plugin-await-add-trycatch]:optionsneedtobeanobject.');
}

//判断父路径中是否已存在try语句,若存在直接返回
if(path.findParent((p)=>p.isTryStatement())){
returnfalse;
}

//合并插件的选项
constoptions=mergeOptions(this.opts);

//获取编译目标文件的路径,如:E:myappsrcApp.vue
constfilePath=this.filename||this.file.opts.filename||'unknown';

//在排除列表的文件不编译
if(matchesFile(options.exclude,filePath)){
return;
}

//如果设置了include,只编译include中的文件
if(options.include.length&&!matchesFile(options.include,filePath)){
return;
}

//获取当前的await节点
letnode=path.node;

//在父路径节点中查找声明async函数的节点
//async函数分为4种情况:函数声明||箭头函数||函数表达式||对象的方法
constasyncPath=path.findParent((p)=>p.node.async&&(p.isFunctionDeclaration()||p.isArrowFunctionExpression()||p.isFunctionExpression()||p.isObjectMethod()));

//获取async的方法名
letasyncName='';

lettype=asyncPath.node.type;

switch(type){
//1️⃣函数表达式
//情况1:普通函数,如constfunc=asyncfunction(){}
//情况2:箭头函数,如constfunc=async()=>{}
case'FunctionExpression':
case'ArrowFunctionExpression':
//使用path.getSibling(index)来获得同级的id路径
letidentifier=asyncPath.getSibling('id');
//获取func方法名
asyncName=identifier&&identifier.node?identifier.node.name:'';
break;

//2️⃣函数声明,如asyncfunctionfn2(){}
case'FunctionDeclaration':
asyncName=(asyncPath.node.id&&asyncPath.node.id.name)||'';
break;

//3️⃣async函数作为对象的方法,如vue项目中,在methods中定义的方法:methods:{asyncfunc(){}}
case'ObjectMethod':
asyncName=asyncPath.node.key.name||'';
break;
}

//若asyncName不存在,通过argument.callee获取当前执行函数的name
letfuncName=asyncName||(node.argument.callee&&node.argument.callee.name)||'';

consttemp=template(tryTemplate);

//给模版增加key,添加console.log打印信息
lettempArgumentObj={
//通过types.stringLiteral创建字符串字面量
CatchError:types.stringLiteral(catchConsole(filePath,funcName,options.customLog))
};

//通过temp创建try语句
lettryNode=temp(tempArgumentObj);

//获取async节点(父节点)的函数体
letinfo=asyncPath.node.body;

//将父节点原来的函数体放到try语句中
tryNode.block.body.push(...info.body);

//将父节点的内容替换成新创建的try语句
info.body=[tryNode];
}
};
return{
name:'babel-plugin-await-add-trycatch',
visitor
};
};

util.js

constmerge=require('deepmerge');

//定义try语句模板
lettryTemplate=`
try{
}catch(e){
console.log(CatchError,e)
}`;

/*
*catch要打印的信息
*@param{string}filePath-当前执行文件的路径
*@param{string}funcName-当前执行方法的名称
*@param{string}customLog-用户自定义的打印信息
*/
letcatchConsole=(filePath,funcName,customLog)=>`
filePath:${filePath}
funcName:${funcName}
${customLog}:`;

//默认配置
constdefaultOptions={
customLog:'Error',
exclude:['node_modules'],
include:[]
};

//判断执行的file文件是否在exclude/include选项内
functionmatchesFile(list,filename){
returnlist.find((name)=>name&&filename.includes(name));
}

//合并选项
functionmergeOptions(options){
let{exclude,include}=options;
if(exclude)options.exclude=toArray(exclude);
if(include)options.include=toArray(include);
//使用merge进行合并
returnmerge.all([defaultOptions,options]);
}

functiontoArray(value){
returnArray.isArray(value)?value:[value];
}

module.exports={
tryTemplate,
catchConsole,
defaultOptions,
mergeOptions,
matchesFile,
toArray
};

npm网站搜索babel-plugin-await-add-trycatch以上就是关于“怎么给所有的async函数添加try/catch”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注百云主机行业资讯频道。

相关推荐: php三个数组如何求总平均数

本篇内容介绍了“php三个数组如何求总平均数”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!实现步骤:1、用array_sum()和“+”运算符获取三个数组的总元…

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

(0)
打赏 微信扫一扫 微信扫一扫
上一篇 02/17 17:25
下一篇 02/17 20:08

相关推荐