真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

怎么給所有的async函數(shù)添加try/catch

這篇“怎么給所有的async函數(shù)添加try/catch”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內(nèi)容,內(nèi)容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“怎么給所有的async函數(shù)添加try/catch”文章吧。

目前成都創(chuàng)新互聯(lián)已為近1000家的企業(yè)提供了網(wǎng)站建設、域名、網(wǎng)站空間、網(wǎng)站托管、企業(yè)網(wǎng)站設計、藤縣網(wǎng)站維護等服務,公司將堅持客戶導向、應用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。

async如果不加 try/catch 會發(fā)生什么事?

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

導致瀏覽器報錯:一個未捕獲的錯誤

怎么給所有的async函數(shù)添加try/catch

在開發(fā)過程中,為了保證系統(tǒng)健壯性,或者是為了捕獲異步的錯誤,需要頻繁的在 async 函數(shù)中添加 try/catch,避免出現(xiàn)上述示例的情況

可是我很懶,不想一個個加,懶惰使我們進步?

下面,通過手寫一個babel 插件,來給所有的async函數(shù)添加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\\src\\main.js\nfuncName: fn\nError:", e);
  }
}
fn();

打印的報錯信息:

怎么給所有的async函數(shù)添加try/catch

通過詳細的報錯信息,幫助我們快速找到目標文件和具體的報錯方法,方便去定位問題

babel插件的實現(xiàn)思路

1)借助AST抽象語法樹,遍歷查找代碼中的await關鍵字

2)找到await節(jié)點后,從父路徑中查找聲明的async函數(shù),獲取該函數(shù)的body(函數(shù)中包含的代碼)

3)創(chuàng)建try/catch語句,將原來async的body放入其中

4)最后將async的body替換成創(chuàng)建的try/catch語句

babel的核心:AST

先聊聊 AST 這個帥小伙?,不然后面的開發(fā)流程走不下去

AST是代碼的樹形結構,生成 AST 分為兩個階段:詞法分析語法分析

詞法分析

詞法分析階段把字符串形式的代碼轉換為令牌(tokens),可以把tokens看作是一個扁平的語法片段數(shù)組,描述了代碼片段在整個代碼中的位置和記錄當前值的一些信息

比如let a = 1,對應的AST是這樣的

怎么給所有的async函數(shù)添加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 的類型叫函數(shù)聲明;
      "id": { // id 為函數(shù)聲明的 id
        "type": "Identifier", // 標識符 類型
        "name": "demo" // 標識符 具有名字 
      },
      "expression": false,
      "generator": false,
      "async": false, // 代表是否 是 async function
      "params": [ // 同級 函數(shù)的參數(shù) 
        {
          "type": "Identifier",// 參數(shù)類型也是 Identifier
          "name": "n"
        }
      ],
      "body": { // 函數(shù)體內(nèi)容 整個格式呈現(xiàn)一種樹的格式
        "type": "BlockStatement", // 整個函數(shù)體內(nèi)容 為一個塊狀代碼塊類型
        "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 節(jié)點類型對照表

類型原名稱中文名稱描述
Program程序主體整段代碼的主體
VariableDeclaration變量聲明聲明一個變量,例如 var let const
FunctionDeclaration函數(shù)聲明聲明一個函數(shù),例如 function
ExpressionStatement表達式語句通常是調(diào)用一個函數(shù),例如 console.log()
BlockStatement塊語句包裹在 {} 塊內(nèi)的代碼,例如 if (condition){var a = 1;}
BreakStatement中斷語句通常指 break
ContinueStatement持續(xù)語句通常指 continue
ReturnStatement返回語句通常指 return
SwitchStatementSwitch 語句通常指 Switch Case 語句中的 Switch
IfStatementIf 控制流語句控制流語句,通常指 if(condition){}else{}
Identifier標識符標識,例如聲明變量時 var identi = 5 中的 identi
CallExpression調(diào)用表達式通常指調(diào)用一個函數(shù),例如 console.log()
BinaryExpression二進制表達式通常指運算,例如 1+2
MemberExpression成員表達式通常指調(diào)用對象的成員,例如 console 對象的 log 成員
ArrayExpression數(shù)組表達式通常指一個數(shù)組,例如 [1, 3, 5]
FunctionExpression函數(shù)表達式例如const func = function () {}
ArrowFunctionExpression箭頭函數(shù)表達式例如const func = ()=> {}
AwaitExpressionawait表達式例如let val = await f()
ObjectMethod對象中定義的方法例如 let obj = { fn () {} }
NewExpressionNew 表達式通常指使用 New 關鍵詞
AssignmentExpression賦值表達式通常指將函數(shù)的返回值賦值給變量
UpdateExpression更新表達式通常指更新成員值,例如 i++
Literal字面量字面量
BooleanLiteral布爾型字面量布爾值,例如 true false
NumericLiteral數(shù)字型字面量數(shù)字,例如 100
StringLiteral字符型字面量字符串,例如 vansenb
SwitchCaseCase 語句通常指 Switch 語句中的 Case

await節(jié)點對應的AST結構

1)原始代碼

async function fn() {
   await f()
}

對應的AST結構

怎么給所有的async函數(shù)添加try/catch

2)增加try catch后的代碼

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

對應的AST結構

怎么給所有的async函數(shù)添加try/catch

通過AST結構對比,插件的核心就是將原始函數(shù)的body放到try語句中

babel插件開發(fā)

插件的基本格式示例

module.exports = function (babel) {
   let t = babel.type
   return { 
     visitor: {
       // 設置需要范圍的節(jié)點類型
       CallExression: (path, state) => { 
         do soming ……
       }
     }
   }
 }

1)通過 babel 拿到 types 對象,操作 AST 節(jié)點,比如創(chuàng)建、校驗、轉變等

2)visitor:定義了一個訪問者,可以設置需要訪問的節(jié)點類型,當訪問到目標節(jié)點后,做相應的處理來實現(xiàn)插件的功能

尋找await節(jié)點

回到業(yè)務需求,現(xiàn)在需要找到await節(jié)點,可以通過AwaitExpression表達式獲取

module.exports = function (babel) {
   let t = babel.type
   return { 
     visitor: {
       // 設置AwaitExpression
       AwaitExpression(path) {
         // 獲取當前的await節(jié)點
         let node = path.node;
       }
     }
   }
 }

向上查找 async 函數(shù)

通過findParent方法,在父節(jié)點中搜尋 async 節(jié)點

// async節(jié)點的屬性為true
const asyncPath = path.findParent(p => p.node.async)

async 節(jié)點的AST結構

怎么給所有的async函數(shù)添加try/catch

這里要注意,async 函數(shù)分為4種情況:函數(shù)聲明 、箭頭函數(shù) 、函數(shù)表達式 、函數(shù)為對象的方法

// 1??:函數(shù)聲明
async function fn() {
  await f()
}

// 2??:函數(shù)表達式
const fn = async function () {
  await f()
};

// 3??:箭頭函數(shù)
const fn = async () => {
  await f()
};

// 4??:async函數(shù)定義在對象中
const obj = {
  async fn() {
      await f()
  }
}

需要對這幾種情況進行分別判斷

module.exports = function (babel) {
   let t = babel.type
   return { 
     visitor: {
       // 設置AwaitExpression
       AwaitExpression(path) {
         // 獲取當前的await節(jié)點
         let node = path.node;
         // 查找async函數(shù)的節(jié)點
         const asyncPath = path.findParent((p) => p.node.async && (p.isFunctionDeclaration() || p.isArrowFunctionExpression() || p.isFunctionExpression() || p.isObjectMethod()));
       }
     }
   }
 }

利用babel-template生成try/catch節(jié)點

babel-template可以用以字符串形式的代碼來構建AST樹節(jié)點,快速優(yōu)雅開發(fā)插件

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

// 定義try/catch語句模板
let tryTemplate = `
try {
} catch (e) {
console.log(CatchError:e)
}`;

// 創(chuàng)建模板
const temp = template(tryTemplate);

// 給模版增加key,添加console.log打印信息
let tempArgumentObj = {
   // 通過types.stringLiteral創(chuàng)建字符串字面量
   CatchError: types.stringLiteral('Error')
};

// 通過temp創(chuàng)建try語句的AST節(jié)點
let tryNode = temp(tempArgumentObj);

async函數(shù)體替換成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);
         
         // 獲取父節(jié)點的函數(shù)體body
         let info = asyncPath.node.body;

         // 將函數(shù)體放到try語句的body中
         tryNode.block.body.push(...info.body);

         // 將父節(jié)點的body替換成新創(chuàng)建的try語句
         info.body = [tryNode];
       }
     }
   }
 }

到這里,插件的基本結構已經(jīng)成型,但還有點問題,如果函數(shù)已存在try/catch,該怎么處理判斷呢?

若函數(shù)已存在try/catch,則不處理

// 示例代碼,不再添加try/catch
async 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\src\App.vue
const filePath = this.filename || this.file.opts.filename || 'unknown';

獲取報錯的方法名稱

// 定義方法名
let asyncName = '';

// 獲取async節(jié)點的type類型
let type = asyncPath.node.type;

switch (type) {
// 1??函數(shù)表達式
// 情況1:普通函數(shù),如const func = async function () {}
// 情況2:箭頭函數(shù),如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??函數(shù)聲明,如async function fn2() {}
case 'FunctionDeclaration':
  asyncName = (asyncPath.node.id && asyncPath.node.id.name) || '';
  break;

// 3??async函數(shù)作為對象的方法,如vue項目中,在methods中定義的方法: methods: { async func() {} }
case 'ObjectMethod':
  asyncName = asyncPath.node.key.name || '';
  break;
}

// 若asyncName不存在,通過argument.callee獲取當前執(zhí)行函數(shù)的name
let funcName = asyncName || (node.argument.callee && node.argument.callee.name) || '';

添加用戶選項

用戶引入插件時,可以設置exclude、include、 customLog選項

exclude: 設置需要排除的文件,不對該文件進行處理

include: 設置需要處理的文件,只對該文件進行處理

customLog: 用戶自定義的打印信息

最終代碼

入口文件index.js

// babel-template 用于將字符串形式的代碼來構建AST樹節(jié)點
const template = require('babel-template');

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

module.exports = function (babel) {
  // 通過babel 拿到 types 對象,操作 AST 節(jié)點,比如創(chuàng)建、校驗、轉變等
  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節(jié)點
      let node = path.node;

      // 在父路徑節(jié)點中查找聲明 async 函數(shù)的節(jié)點
      // async 函數(shù)分為4種情況:函數(shù)聲明 || 箭頭函數(shù) || 函數(shù)表達式 || 對象的方法
      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??函數(shù)表達式
        // 情況1:普通函數(shù),如const func = async function () {}
        // 情況2:箭頭函數(shù),如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??函數(shù)聲明,如async function fn2() {}
        case 'FunctionDeclaration':
          asyncName = (asyncPath.node.id && asyncPath.node.id.name) || '';
          break;

        // 3??async函數(shù)作為對象的方法,如vue項目中,在methods中定義的方法: methods: { async func() {} }
        case 'ObjectMethod':
          asyncName = asyncPath.node.key.name || '';
          break;
      }

      // 若asyncName不存在,通過argument.callee獲取當前執(zhí)行函數(shù)的name
      let funcName = asyncName || (node.argument.callee && node.argument.callee.name) || '';

      const temp = template(tryTemplate);

      // 給模版增加key,添加console.log打印信息
      let tempArgumentObj = {
        // 通過types.stringLiteral創(chuàng)建字符串字面量
        CatchError: types.stringLiteral(catchConsole(filePath, funcName, options.customLog))
      };

      // 通過temp創(chuàng)建try語句
      let tryNode = temp(tempArgumentObj);

      // 獲取async節(jié)點(父節(jié)點)的函數(shù)體
      let info = asyncPath.node.body;

      // 將父節(jié)點原來的函數(shù)體放到try語句中
      tryNode.block.body.push(...info.body);

      // 將父節(jié)點的內(nèi)容替換成新創(chuàng)建的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)
}`;

/*
 * catch要打印的信息
 * @param {string} filePath - 當前執(zhí)行文件的路徑
 * @param {string} funcName - 當前執(zhí)行方法的名稱
 * @param {string} customLog - 用戶自定義的打印信息
 */
let catchConsole = (filePath, funcName, customLog) => `
filePath: ${filePath}
funcName: ${funcName}
${customLog}:`;

// 默認配置
const defaultOptions = {
  customLog: 'Error',
  exclude: ['node_modules'],
  include: []
};

// 判斷執(zhí)行的file文件 是否在 exclude/include 選項內(nèi)
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網(wǎng)站搜索babel-plugin-await-add-trycatch

怎么給所有的async函數(shù)添加try/catch

以上就是關于“怎么給所有的async函數(shù)添加try/catch”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關的知識內(nèi)容,請關注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


當前題目:怎么給所有的async函數(shù)添加try/catch
瀏覽地址:http://weahome.cn/article/jsshjj.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部