Skip to content
On this page

如何给所有的 async 函数添加 try/catch?

前言

阿里三面的时候被问到了这个问题,当时思路虽然正确,可惜表述的不够清晰

后来花了一些时间整理了下思路,那么如何实现给所有的 async 函数添加 try/catch 呢?

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

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

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

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

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

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

babel 插件的最终效果

原始代码:

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

使用插件转化后的代码:

js
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\\src\\main.js\nfuncName: fn\nError:", e);
  }
}
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\\src\\main.js\nfuncName: fn\nError:", e);
  }
}
fn();

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

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 看作是一个扁平的语法片段数组,描述了代码片段在整个代码中的位置和记录当前值的一些信息

语法分析

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

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

AST 在线查看工具:AST explorer

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

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

转化成 AST 的结构

js
{
  "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"
}
{
  "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)
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)原始代码

js
async function fn() {
  await f();
}
async function fn() {
  await f();
}

2)增加 try catch 后的代码

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

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

babel 插件开发

作者曾在《「历时 8 个月」10 万字前端知识体系总结(工程化篇)🔥》中聊过如何开发一个 babel 插件

这里简单回顾一下

插件的基本格式示例

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

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

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

寻找 await 节点

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

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

向上查找 async 函数

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

js
// async节点的属性为true
const asyncPath = path.findParent((p) => p.node.async);
// async节点的属性为true
const asyncPath = path.findParent((p) => p.node.async);

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

js
// 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();
  },
};
// 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();
  },
};

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

js
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())
        );
      },
    },
  };
};
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 树节点,快速优雅开发插件

js
// 引入babel-template
const 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);
// 引入babel-template
const 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 语句

js
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];
      },
    },
  };
};
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,则不处理

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

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

js
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];
      },
    },
  };
};
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,方便快速定位问题

获取文件路径

js
// 获取编译目标文件的路径,如:E:\myapp\src\App.vue
const filePath = this.filename || this.file.opts.filename || "unknown";
// 获取编译目标文件的路径,如:E:\myapp\src\App.vue
const filePath = this.filename || this.file.opts.filename || "unknown";

获取报错的方法名称

js
// 定义方法名
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获取当前执行函数的name
let funcName =
  asyncName || (node.argument.callee && node.argument.callee.name) || "";
// 定义方法名
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获取当前执行函数的name
let funcName =
  asyncName || (node.argument.callee && node.argument.callee.name) || "";

添加用户选项

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

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

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

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

最终代码

入口文件 index.js

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\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,
  };
};
// 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\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

js
const merge = require("deepmerge");

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

/*
 * catch要打印的信息
 * @param {string} filePath - 当前执行文件的路径
 * @param {string} funcName - 当前执行方法的名称
 * @param {string} customLog - 用户自定义的打印信息
 */
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,
};
const merge = require("deepmerge");

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

/*
 * catch要打印的信息
 * @param {string} filePath - 当前执行文件的路径
 * @param {string} funcName - 当前执行方法的名称
 * @param {string} customLog - 用户自定义的打印信息
 */
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,
};

本文章作者的 github 仓库