欧美亚洲中文,在线国自产视频,欧洲一区在线观看视频,亚洲综合中文字幕在线观看

      1. <dfn id="rfwes"></dfn>
          <object id="rfwes"></object>
        1. 站長資訊網(wǎng)
          最全最豐富的資訊網(wǎng)站

          面試題:如何給所有的async函數(shù)添加try/catch?

          面試題:如何給所有的async函數(shù)添加try/catch?

          前端(vue)入門到精通課程:進(jìn)入學(xué)習(xí)
          Apipost = Postman + Swagger + Mock + Jmeter 超好用的API調(diào)試工具:點(diǎn)擊使用

          去阿里面試,三面的時(shí)候被問到了這個(gè)問題,當(dāng)時(shí)思路雖然正確,可惜表述的不夠清晰

          后來花了一些時(shí)間整理了下思路,那么如何實(shí)現(xiàn)給所有的async函數(shù)添加try/catch呢?

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

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

          導(dǎo)致瀏覽器報(bào)錯(cuò):一個(gè)未捕獲的錯(cuò)誤

          面試題:如何給所有的async函數(shù)添加try/catch?

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

          可是我很懶,不想一個(gè)個(gè)加,懶惰使我們進(jìn)步?

          下面,通過手寫一個(gè)babel 插件,來給所有的async函數(shù)添加try/catch

          babel插件的最終效果

          原始代碼:

          async function fn() {   await new Promise((resolve, reject) => reject('報(bào)錯(cuò)'));   await new Promise((resolve) => resolve(1));   console.log('do something...'); } fn();
          登錄后復(fù)制

          使用插件轉(zhuǎn)化后的代碼:

          async function fn() {   try {     await new Promise((resolve, reject) => reject('報(bào)錯(cuò)'));     await new Promise(resolve => resolve(1));     console.log('do something...');   } catch (e) {     console.log("nfilePath: E:\myapp\src\main.jsnfuncName: fnnError:", e);   } } fn();
          登錄后復(fù)制

          打印的報(bào)錯(cuò)信息:

          面試題:如何給所有的async函數(shù)添加try/catch?

          通過詳細(xì)的報(bào)錯(cuò)信息,幫助我們快速找到目標(biāo)文件和具體的報(bào)錯(cuò)方法,方便去定位問題

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

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

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

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

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

          babel的核心:AST

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

          AST是代碼的樹形結(jié)構(gòu),生成 AST 分為兩個(gè)階段:詞法分析語法分析

          詞法分析

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

          比如let a = 1,對應(yīng)的AST是這樣的

          面試題:如何給所有的async函數(shù)添加try/catch?

          語法分析

          語法分析階段會(huì)把token轉(zhuǎn)換成 AST 的形式,這個(gè)階段會(huì)使用token中的信息把它們轉(zhuǎn)換成一個(gè) AST 的表述結(jié)構(gòu),使用type屬性記錄當(dāng)前的類型

          例如 let 代表著一個(gè)變量聲明的關(guān)鍵字,所以它的 type 為 VariableDeclaration,而 a = 1 會(huì)作為 let 的聲明描述,它的 type 為 VariableDeclarator

          AST在線查看工具:AST explorer

          再舉個(gè)?,加深對AST的理解

          function demo(n) {   return n * n; }
          登錄后復(fù)制

          轉(zhuǎn)化成AST的結(jié)構(gòu)

          {   "type": "Program", // 整段代碼的主體   "body": [     {       "type": "FunctionDeclaration", // function 的類型叫函數(shù)聲明;       "id": { // id 為函數(shù)聲明的 id         "type": "Identifier", // 標(biāo)識(shí)符 類型         "name": "demo" // 標(biāo)識(shí)符 具有名字        },       "expression": false,       "generator": false,       "async": false, // 代表是否 是 async function       "params": [ // 同級(jí) 函數(shù)的參數(shù)          {           "type": "Identifier",// 參數(shù)類型也是 Identifier           "name": "n"         }       ],       "body": { // 函數(shù)體內(nèi)容 整個(gè)格式呈現(xiàn)一種樹的格式         "type": "BlockStatement", // 整個(gè)函數(shù)體內(nèi)容 為一個(gè)塊狀代碼塊類型         "body": [           {             "type": "ReturnStatement", // return 類型             "argument": {               "type": "BinaryExpression",// BinaryExpression 二進(jìn)制表達(dá)式類型               "start": 30,               "end": 35,               "left": { // 分左 右 中 結(jié)構(gòu)                 "type": "Identifier",                  "name": "n"               },               "operator": "*", // 屬于操作符               "right": {                 "type": "Identifier",                 "name": "n"               }             }           }         ]       }     }   ],   "sourceType": "module" }
          登錄后復(fù)制

          常用的 AST 節(jié)點(diǎn)類型對照表

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

          await節(jié)點(diǎn)對應(yīng)的AST結(jié)構(gòu)

          1)原始代碼

          async function fn() {    await f() }
          登錄后復(fù)制

          對應(yīng)的AST結(jié)構(gòu)

          面試題:如何給所有的async函數(shù)添加try/catch?

          2)增加try catch后的代碼

          async function fn() {     try {         await f()     } catch (e) {         console.log(e)     } }
          登錄后復(fù)制

          對應(yīng)的AST結(jié)構(gòu)

          面試題:如何給所有的async函數(shù)添加try/catch?

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

          babel插件開發(fā)

          我曾在之前的文章中聊過如何開發(fā)一個(gè)babel插件

          這里簡單回顧一下

          插件的基本格式示例

          module.exports = function (babel) {    let t = babel.type    return {       visitor: {        // 設(shè)置需要范圍的節(jié)點(diǎn)類型        CallExression: (path, state) => {           do soming ……        }      }    }  }
          登錄后復(fù)制

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

          2)visitor:定義了一個(gè)訪問者,可以設(shè)置需要訪問的節(jié)點(diǎn)類型,當(dāng)訪問到目標(biāo)節(jié)點(diǎn)后,做相應(yīng)的處理來實(shí)現(xiàn)插件的功能

          尋找await節(jié)點(diǎn)

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

          module.exports = function (babel) {    let t = babel.type    return {       visitor: {        // 設(shè)置AwaitExpression        AwaitExpression(path) {          // 獲取當(dāng)前的await節(jié)點(diǎn)          let node = path.node;        }      }    }  }
          登錄后復(fù)制

          向上查找 async 函數(shù)

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

          // async節(jié)點(diǎn)的屬性為true const asyncPath = path.findParent(p => p.node.async)
          登錄后復(fù)制

          async 節(jié)點(diǎn)的AST結(jié)構(gòu)

          面試題:如何給所有的async函數(shù)添加try/catch?

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

          // 1️⃣:函數(shù)聲明 async function fn() {   await f() }  // 2️⃣:函數(shù)表達(dá)式 const fn = async function () {   await f() };  // 3️⃣:箭頭函數(shù) const fn = async () => {   await f() };  // 4️⃣:async函數(shù)定義在對象中 const obj = {   async fn() {       await f()   } }
          登錄后復(fù)制

          需要對這幾種情況進(jìn)行分別判斷

          module.exports = function (babel) {    let t = babel.type    return {       visitor: {        // 設(shè)置AwaitExpression        AwaitExpression(path) {          // 獲取當(dāng)前的await節(jié)點(diǎn)          let node = path.node;          // 查找async函數(shù)的節(jié)點(diǎn)          const asyncPath = path.findParent((p) => p.node.async && (p.isFunctionDeclaration() || p.isArrowFunctionExpression() || p.isFunctionExpression() || p.isObjectMethod()));        }      }    }  }
          登錄后復(fù)制

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

          babel-template可以用以字符串形式的代碼來構(gòu)建AST樹節(jié)點(diǎn),快速優(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é)點(diǎn) let tryNode = temp(tempArgumentObj);
          登錄后復(fù)制

          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é)點(diǎn)的函數(shù)體body          let info = asyncPath.node.body;           // 將函數(shù)體放到try語句的body中          tryNode.block.body.push(...info.body);           // 將父節(jié)點(diǎn)的body替換成新創(chuàng)建的try語句          info.body = [tryNode];        }      }    }  }
          登錄后復(fù)制

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

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

          // 示例代碼,不再添加try/catch async function fn() {     try {         await f()     } catch (e) {         console.log(e)     } }
          登錄后復(fù)制

          通過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];        }      }    }  }
          登錄后復(fù)制

          添加報(bào)錯(cuò)信息

          獲取報(bào)錯(cuò)時(shí)的文件路徑 filePath 和方法名稱 funcName,方便快速定位問題

          獲取文件路徑

          // 獲取編譯目標(biāo)文件的路徑,如:E:myappsrcApp.vue const filePath = this.filename || this.file.opts.filename || 'unknown';
          登錄后復(fù)制

          獲取報(bào)錯(cuò)的方法名稱

          // 定義方法名 let asyncName = '';  // 獲取async節(jié)點(diǎn)的type類型 let type = asyncPath.node.type;  switch (type) { // 1️⃣函數(shù)表達(dá)式 // 情況1:普通函數(shù),如const func = async function () {} // 情況2:箭頭函數(shù),如const func = async () => {} case 'FunctionExpression': case 'ArrowFunctionExpression':   // 使用path.getSibling(index)來獲得同級(jí)的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項(xiàng)目中,在methods中定義的方法: methods: { async func() {} } case 'ObjectMethod':   asyncName = asyncPath.node.key.name || '';   break; }  // 若asyncName不存在,通過argument.callee獲取當(dāng)前執(zhí)行函數(shù)的name let funcName = asyncName || (node.argument.callee && node.argument.callee.name) || '';
          登錄后復(fù)制

          添加用戶選項(xiàng)

          用戶引入插件時(shí),可以設(shè)置exclude、include、 customLog選項(xiàng)

          exclude: 設(shè)置需要排除的文件,不對該文件進(jìn)行處理

          include: 設(shè)置需要處理的文件,只對該文件進(jìn)行處理

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

          最終代碼

          入口文件index.js

          // babel-template 用于將字符串形式的代碼來構(gòu)建AST樹節(jié)點(diǎn) const template = require('babel-template');  const { tryTemplate, catchConsole, mergeOptions, matchesFile } = require('./util');  module.exports = function (babel) {   // 通過babel 拿到 types 對象,操作 AST 節(jié)點(diǎn),比如創(chuàng)建、校驗(yàn)、轉(zhuǎn)變等   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;       }        // 合并插件的選項(xiàng)       const options = mergeOptions(this.opts);        // 獲取編譯目標(biāo)文件的路徑,如:E:myappsrcApp.vue       const filePath = this.filename || this.file.opts.filename || 'unknown';        // 在排除列表的文件不編譯       if (matchesFile(options.exclude, filePath)) {         return;       }        // 如果設(shè)置了include,只編譯include中的文件       if (options.include.length && !matchesFile(options.include, filePath)) {         return;       }        // 獲取當(dāng)前的await節(jié)點(diǎn)       let node = path.node;        // 在父路徑節(jié)點(diǎn)中查找聲明 async 函數(shù)的節(jié)點(diǎn)       // async 函數(shù)分為4種情況:函數(shù)聲明 || 箭頭函數(shù) || 函數(shù)表達(dá)式 || 對象的方法       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ù)表達(dá)式         // 情況1:普通函數(shù),如const func = async function () {}         // 情況2:箭頭函數(shù),如const func = async () => {}         case 'FunctionExpression':         case 'ArrowFunctionExpression':           // 使用path.getSibling(index)來獲得同級(jí)的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項(xiàng)目中,在methods中定義的方法: methods: { async func() {} }         case 'ObjectMethod':           asyncName = asyncPath.node.key.name || '';           break;       }        // 若asyncName不存在,通過argument.callee獲取當(dāng)前執(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é)點(diǎn)(父節(jié)點(diǎn))的函數(shù)體       let info = asyncPath.node.body;        // 將父節(jié)點(diǎn)原來的函數(shù)體放到try語句中       tryNode.block.body.push(...info.body);        // 將父節(jié)點(diǎn)的內(nèi)容替換成新創(chuàng)建的try語句       info.body = [tryNode];     }   };   return {     name: 'babel-plugin-await-add-trycatch',     visitor   }; };
          登錄后復(fù)制

          util.js

          const merge = require('deepmerge');  // 定義try語句模板 let tryTemplate = ` try { } catch (e) { console.log(CatchError,e) }`;  /*  * catch要打印的信息  * @param {string} filePath - 當(dāng)前執(zhí)行文件的路徑  * @param {string} funcName - 當(dāng)前執(zhí)行方法的名稱  * @param {string} customLog - 用戶自定義的打印信息  */ let catchConsole = (filePath, funcName, customLog) => ` filePath: ${filePath} funcName: ${funcName} ${customLog}:`;  // 默認(rèn)配置 const defaultOptions = {   customLog: 'Error',   exclude: ['node_modules'],   include: [] };  // 判斷執(zhí)行的file文件 是否在 exclude/include 選項(xiàng)內(nèi) function matchesFile(list, filename) {   return list.find((name) => name && filename.includes(name)); }  // 合并選項(xiàng) function mergeOptions(options) {   let { exclude, include } = options;   if (exclude) options.exclude = toArray(exclude);   if (include) options.include = toArray(include);   // 使用merge進(jìn)行合并   return merge.all([defaultOptions, options]); }  function toArray(value) {   return Array.isArray(value) ? value : [value]; }  module.exports = {   tryTemplate,   catchConsole,   defaultOptions,   mergeOptions,   matchesFile,   toArray };
          登錄后復(fù)制

          github倉庫

          babel插件的安裝使用

          npm網(wǎng)站搜索babel-plugin-await-add-trycatch

          面試題:如何給所有的async函數(shù)添加try/catch?

          有興趣的朋友可以下載玩一玩

          babel-plugin-await-add-trycatch

          總結(jié)

          通過開發(fā)這個(gè)babel插件,了解很多 AST 方面的知識(shí),了解 babel 的原理。實(shí)際開發(fā)中,大家可以結(jié)合具體的業(yè)務(wù)需求開發(fā)自己的插件

          贊(0)
          分享到: 更多 (0)
          網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)