我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

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

async如果不加 try/catch 会发生什么事?

// 示例async function fn() {  let value = await new Promise((resolve, reject) => {    reject('failure');  });  console.log('do something...');}fn()

导致浏览器报错:一个未捕获的错误

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

在开发过程中,为了保证系统健壮性,或者是为了捕获异步的错误,需要频繁的在 async 函数中添加 try/catch,避免出现上述示例的情况

可是我很懒,不想一个个加,懒惰使我们进步?

下面,通过手写一个babel 插件,来给所有的async函数添加try/catch

babel插件的最终效果

原始代码:

async function fn() {  await new Promise((resolve, reject) => reject('报错'));  await new Promise((resolve) => resolve(1));  console.log('do something...');}fn();

使用插件转化后的代码:

async function fn() {  try {    await new Promise((resolve, reject) => reject('报错'));    await new Promise(resolve => resolve(1));    console.log('do something...');  } catch (e) {    console.log("\nfilePath: E:\\myapp\\class="lazy" data-src\\main.js\nfuncName: fn\nError:", e);  }}fn();

打印的报错信息:

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

通过详细的报错信息,帮助我们快速找到目标文件和具体的报错方法,方便去定位问题

babel插件的实现思路

1)借助AST抽象语法树,遍历查找代码中的await关键字

2)找到await节点后,从父路径中查找声明的async函数,获取该函数的body(函数中包含的代码)

3)创建try/catch语句,将原来async的body放入其中

4)最后将async的body替换成创建的try/catch语句

babel的核心:AST

先聊聊 AST 这个帅小伙?,不然后面的开发流程走不下去

AST是代码的树形结构,生成 AST 分为两个阶段:词法分析语法分析

词法分析

词法分析阶段把字符串形式的代码转换为令牌(tokens) ,可以把tokens看作是一个扁平的语法片段数组,描述了代码片段在整个代码中的位置和记录当前值的一些信息

比如let a = 1,对应的AST是这样的

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

语法分析

语法分析阶段会把token转换成 AST 的形式,这个阶段会使用token中的信息把它们转换成一个 AST 的表述结构,使用type属性记录当前的类型

例如 let 代表着一个变量声明的关键字,所以它的 type 为 VariableDeclaration,而 a = 1 会作为 let 的声明描述,它的 type 为 VariableDeclarator

AST在线查看工具:AST explorer

再举个?,加深对AST的理解

function demo(n) {  return n * n;}

转化成AST的结构

{  "type": "Program", // 整段代码的主体  "body": [    {      "type": "FunctionDeclaration", // function 的类型叫函数声明;      "id": { // id 为函数声明的 id        "type": "Identifier", // 标识符 类型        "name": "demo" // 标识符 具有名字       },      "expression": false,      "generator": false,      "async": false, // 代表是否 是 async function      "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"}

常用的 AST 节点类型对照表

类型原名称中文名称描述
Program程序主体整段代码的主体
VariableDeclaration变量声明声明一个变量,例如 var let const
FunctionDeclaration函数声明声明一个函数,例如 function
ExpressionStatement表达式语句通常是调用一个函数,例如 console.log()
BlockStatement块语句包裹在 {} 块内的代码,例如 if (condition){var a = 1;}
BreakStatement中断语句通常指 break
ContinueStatement持续语句通常指 continue
ReturnStatement返回语句通常指 return
SwitchStatementSwitch 语句通常指 Switch Case 语句中的 Switch
IfStatementIf 控制流语句控制流语句,通常指 if(condition){}else{}
Identifier标识符标识,例如声明变量时 var identi = 5 中的 identi
CallExpression调用表达式通常指调用一个函数,例如 console.log()
BinaryExpression二进制表达式通常指运算,例如 1+2
MemberExpression成员表达式通常指调用对象的成员,例如 console 对象的 log 成员
ArrayExpression数组表达式通常指一个数组,例如 [1, 3, 5]
FunctionExpression函数表达式例如const func = function () {}
ArrowFunctionExpression箭头函数表达式例如const func = ()=> {}
AwaitExpressionawait表达式例如let val = await f()
ObjectMethod对象中定义的方法例如 let obj = { fn () {} }
NewExpressionNew 表达式通常指使用 New 关键词
AssignmentExpression赋值表达式通常指将函数的返回值赋值给变量
UpdateExpression更新表达式通常指更新成员值,例如 i++
Literal字面量字面量
BooleanLiteral布尔型字面量布尔值,例如 true false
NumericLiteral数字型字面量数字,例如 100
StringLiteral字符型字面量字符串,例如 vansenb
SwitchCaseCase 语句通常指 Switch 语句中的 Case

await节点对应的AST结构

1)原始代码

async function fn() {   await f()}

对应的AST结构

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

2)增加try catch后的代码

async function fn() {    try {        await f()    } catch (e) {        console.log(e)    }}

对应的AST结构

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

通过AST结构对比,插件的核心就是将原始函数的body放到try语句中

babel插件开发

插件的基本格式示例

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

1)通过 babel 拿到 types 对象,操作 AST 节点,比如创建、校验、转变等

2)visitor:定义了一个访问者,可以设置需要访问的节点类型,当访问到目标节点后,做相应的处理来实现插件的功能

寻找await节点

回到业务需求,现在需要找到await节点,可以通过AwaitExpression表达式获取

module.exports = function (babel) {   let t = babel.type   return {      visitor: {       // 设置AwaitExpression       AwaitExpression(path) {         // 获取当前的await节点         let node = path.node;       }     }   } }

向上查找 async 函数

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

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

async 节点的AST结构

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

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

// 1️⃣:函数声明async function fn() {  await f()}// 2️⃣:函数表达式const fn = async function () {  await f()};// 3️⃣:箭头函数const fn = async () => {  await f()};// 4️⃣:async函数定义在对象中const obj = {  async fn() {      await f()  }}

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

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

利用babel-template生成try/catch节点

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

// 引入babel-templateconst template = require('babel-template');// 定义try/catch语句模板let tryTemplate = `try {} catch (e) {console.log(CatchError:e)}`;// 创建模板const temp = template(tryTemplate);// 给模版增加key,添加console.log打印信息let tempArgumentObj = {   // 通过types.stringLiteral创建字符串字面量   CatchError: types.stringLiteral('Error')};// 通过temp创建try语句的AST节点let tryNode = temp(tempArgumentObj);

async函数体替换成try语句

module.exports = function (babel) {   let t = babel.type   return {      visitor: {       AwaitExpression(path) {         let node = path.node;         const asyncPath = path.findParent((p) => p.node.async && (p.isFunctionDeclaration() || p.isArrowFunctionExpression() || p.isFunctionExpression() || p.isObjectMethod()));                  let tryNode = temp(tempArgumentObj);                  // 获取父节点的函数体body         let info = asyncPath.node.body;         // 将函数体放到try语句的body中         tryNode.block.body.push(...info.body);         // 将父节点的body替换成新创建的try语句         info.body = [tryNode];       }     }   } }

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

若函数已存在try/catch,则不处理

// 示例代码,不再添加try/catchasync function fn() {    try {        await f()    } catch (e) {        console.log(e)    }}

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

module.exports = function (babel) {   let t = babel.type   return {      visitor: {       AwaitExpression(path) {               // 判断父路径中是否已存在try语句,若存在直接返回        if (path.findParent((p) => p.isTryStatement())) {          return false;        }                let node = path.node;         const asyncPath = path.findParent((p) => p.node.async && (p.isFunctionDeclaration() || p.isArrowFunctionExpression() || p.isFunctionExpression() || p.isObjectMethod()));         let tryNode = temp(tempArgumentObj);         let info = asyncPath.node.body;         tryNode.block.body.push(...info.body);         info.body = [tryNode];       }     }   } }

添加报错信息

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

获取文件路径

// 获取编译目标文件的路径,如:E:\myapp\class="lazy" data-src\App.vueconst filePath = this.filename || this.file.opts.filename || 'unknown';

获取报错的方法名称

// 定义方法名let asyncName = '';// 获取async节点的type类型let type = asyncPath.node.type;switch (type) {// 1️⃣函数表达式// 情况1:普通函数,如const func = async function () {}// 情况2:箭头函数,如const func = async () => {}case 'FunctionExpression':case 'ArrowFunctionExpression':  // 使用path.getSibling(index)来获得同级的id路径  let identifier = asyncPath.getSibling('id');  // 获取func方法名  asyncName = identifier && identifier.node ? identifier.node.name : '';  break;// 2️⃣函数声明,如async function fn2() {}case 'FunctionDeclaration':  asyncName = (asyncPath.node.id && asyncPath.node.id.name) || '';  break;// 3️⃣async函数作为对象的方法,如vue项目中,在methods中定义的方法: methods: { async func() {} }case 'ObjectMethod':  asyncName = asyncPath.node.key.name || '';  break;}// 若asyncName不存在,通过argument.callee获取当前执行函数的namelet funcName = asyncName || (node.argument.callee && node.argument.callee.name) || '';

添加用户选项

用户引入插件时,可以设置excludeincludecustomLog选项

exclude: 设置需要排除的文件,不对该文件进行处理

include: 设置需要处理的文件,只对该文件进行处理

customLog: 用户自定义的打印信息

最终代码

入口文件index.js

// babel-template 用于将字符串形式的代码来构建AST树节点const template = require('babel-template');const { tryTemplate, catchConsole, mergeOptions, matchesFile } = require('./util');module.exports = function (babel) {  // 通过babel 拿到 types 对象,操作 AST 节点,比如创建、校验、转变等  let types = babel.types;  // visitor:插件核心对象,定义了插件的工作流程,属于访问者模式  const visitor = {    AwaitExpression(path) {      // 通过this.opts 获取用户的配置      if (this.opts && !typeof this.opts === 'object') {        return console.error('[babel-plugin-await-add-trycatch]: options need to be an object.');      }      // 判断父路径中是否已存在try语句,若存在直接返回      if (path.findParent((p) => p.isTryStatement())) {        return false;      }      // 合并插件的选项      const options = mergeOptions(this.opts);      // 获取编译目标文件的路径,如:E:\myapp\class="lazy" data-src\App.vue      const filePath = 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节点      let node = path.node;      // 在父路径节点中查找声明 async 函数的节点      // async 函数分为4种情况:函数声明 || 箭头函数 || 函数表达式 || 对象的方法      const asyncPath = path.findParent((p) => p.node.async && (p.isFunctionDeclaration() || p.isArrowFunctionExpression() || p.isFunctionExpression() || p.isObjectMethod()));      // 获取async的方法名      let asyncName = '';      let type = asyncPath.node.type;      switch (type) {        // 1️⃣函数表达式        // 情况1:普通函数,如const func = async function () {}        // 情况2:箭头函数,如const func = async () => {}        case 'FunctionExpression':        case 'ArrowFunctionExpression':          // 使用path.getSibling(index)来获得同级的id路径          let identifier = asyncPath.getSibling('id');          // 获取func方法名          asyncName = identifier && identifier.node ? identifier.node.name : '';          break;        // 2️⃣函数声明,如async function fn2() {}        case 'FunctionDeclaration':          asyncName = (asyncPath.node.id && asyncPath.node.id.name) || '';          break;        // 3️⃣async函数作为对象的方法,如vue项目中,在methods中定义的方法: methods: { async func() {} }        case 'ObjectMethod':          asyncName = asyncPath.node.key.name || '';          break;      }      // 若asyncName不存在,通过argument.callee获取当前执行函数的name      let funcName = asyncName || (node.argument.callee && node.argument.callee.name) || '';      const temp = template(tryTemplate);      // 给模版增加key,添加console.log打印信息      let tempArgumentObj = {        // 通过types.stringLiteral创建字符串字面量        CatchError: types.stringLiteral(catchConsole(filePath, funcName, options.customLog))      };      // 通过temp创建try语句      let tryNode = temp(tempArgumentObj);      // 获取async节点(父节点)的函数体      let info = asyncPath.node.body;      // 将父节点原来的函数体放到try语句中      tryNode.block.body.push(...info.body);      // 将父节点的内容替换成新创建的try语句      info.body = [tryNode];    }  };  return {    name: 'babel-plugin-await-add-trycatch',    visitor  };};

util.js

const merge = require('deepmerge');// 定义try语句模板let tryTemplate = `try {} catch (e) {console.log(CatchError,e)}`;let catchConsole = (filePath, funcName, customLog) => `filePath: ${filePath}funcName: ${funcName}${customLog}:`;// 默认配置const defaultOptions = {  customLog: 'Error',  exclude: ['node_modules'],  include: []};// 判断执行的file文件 是否在 exclude/include 选项内function matchesFile(list, filename) {  return list.find((name) => name && filename.includes(name));}// 合并选项function mergeOptions(options) {  let { exclude, include } = options;  if (exclude) options.exclude = toArray(exclude);  if (include) options.include = toArray(include);  // 使用merge进行合并  return merge.all([defaultOptions, options]);}function toArray(value) {  return Array.isArray(value) ? value : [value];}module.exports = {  tryTemplate,  catchConsole,  defaultOptions,  mergeOptions,  matchesFile,  toArray};

babel插件的安装使用

npm网站搜索babel-plugin-await-add-trycatch

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

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

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

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

面试题:如何给所有的async函数添加try/catch?

如何给所有的async函数添加try/catch?去阿里面试,三面的时候被问到了这个问题,当时思路虽然正确,可惜表述的不够清晰。后来花了一些时间整理了下思路,下面一起聊聊!
2022-11-22

oracle怎么给已有数据表添加索引

要给已有数据表添加索引,可以使用Oracle的CREATE INDEX语句。以下是添加索引的基本语法:```CREATE INDEX index_nameON table_name (column1, column2, ...);```其中
2023-10-08

怎么给qt的表格添加数据

要给Qt的表格添加数据,可以使用QTableWidget类来创建一个表格,并使用setItem()方法将数据添加到表格中。下面是一个简单的示例,演示如何向Qt的表格添加数据:// 创建一个 QTableWidget 对象QTableW
2023-10-26

mysql怎么给表的指定列添加数据

在MySQL中,可以使用INSERT语句来为表的指定列添加新的数据。语法如下:INSERT INTO table_name (column1, column2, column3, ...) VALUES (value1, value2,
mysql怎么给表的指定列添加数据
2024-04-09

怎么使用Java递归推出给定节点数的所有形状二叉搜索树

这篇文章主要讲解了“怎么使用Java递归推出给定节点数的所有形状二叉搜索树”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么使用Java递归推出给定节点数的所有形状二叉搜索树”吧!题目:给定
2023-06-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录