久久久久久久视色,久久电影免费精品,中文亚洲欧美乱码在线观看,在线免费播放AV片

<center id="vfaef"><input id="vfaef"><table id="vfaef"></table></input></center>

    <p id="vfaef"><kbd id="vfaef"></kbd></p>

    
    
    <pre id="vfaef"><u id="vfaef"></u></pre>

      <thead id="vfaef"><input id="vfaef"></input></thead>

    1. 站長(zhǎng)資訊網(wǎng)
      最全最豐富的資訊網(wǎng)站

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

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

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

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

      后來(lái)花了一些時(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?

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

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

      下面,通過(guò)手寫(xiě)一個(gè)babel 插件,來(lái)給所有的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?

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

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

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

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

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

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

      babel的核心:AST

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

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

      詞法分析

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

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

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

      語(yǔ)法分析

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

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

      AST在線查看工具:AST explorer

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

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

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

      {   "type": "Program", // 整段代碼的主體   "body": [     {       "type": "FunctionDeclaration", // function 的類(lèi)型叫函數(shù)聲明;       "id": { // id 為函數(shù)聲明的 id         "type": "Identifier", // 標(biāo)識(shí)符 類(lèi)型         "name": "demo" // 標(biāo)識(shí)符 具有名字        },       "expression": false,       "generator": false,       "async": false, // 代表是否 是 async function       "params": [ // 同級(jí) 函數(shù)的參數(shù)          {           "type": "Identifier",// 參數(shù)類(lèi)型也是 Identifier           "name": "n"         }       ],       "body": { // 函數(shù)體內(nèi)容 整個(gè)格式呈現(xiàn)一種樹(shù)的格式         "type": "BlockStatement", // 整個(gè)函數(shù)體內(nèi)容 為一個(gè)塊狀代碼塊類(lèi)型         "body": [           {             "type": "ReturnStatement", // return 類(lèi)型             "argument": {               "type": "BinaryExpression",// BinaryExpression 二進(jìn)制表達(dá)式類(lèi)型               "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)類(lèi)型對(duì)照表

      類(lèi)型原名稱(chēng) 中文名稱(chēng) 描述
      Program 程序主體 整段代碼的主體
      VariableDeclaration 變量聲明 聲明一個(gè)變量,例如 var let const
      FunctionDeclaration 函數(shù)聲明 聲明一個(gè)函數(shù),例如 function
      ExpressionStatement 表達(dá)式語(yǔ)句 通常是調(diào)用一個(gè)函數(shù),例如 console.log()
      BlockStatement 塊語(yǔ)句 包裹在 {} 塊內(nèi)的代碼,例如 if (condition){var a = 1;}
      BreakStatement 中斷語(yǔ)句 通常指 break
      ContinueStatement 持續(xù)語(yǔ)句 通常指 continue
      ReturnStatement 返回語(yǔ)句 通常指 return
      SwitchStatement Switch 語(yǔ)句 通常指 Switch Case 語(yǔ)句中的 Switch
      IfStatement If 控制流語(yǔ)句 控制流語(yǔ)句,通常指 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)用對(duì)象的成員,例如 console 對(duì)象的 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 對(duì)象中定義的方法 例如 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 語(yǔ)句 通常指 Switch 語(yǔ)句中的 Case

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

      1)原始代碼

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

      對(duì)應(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ù)制

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

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

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

      babel插件開(kāi)發(fā)

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

      這里簡(jiǎn)單回顧一下

      插件的基本格式示例

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

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

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

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

      回到業(yè)務(wù)需求,現(xiàn)在需要找到await節(jié)點(diǎn),可以通過(guò)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ù)

      通過(guò)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ù)為對(duì)象的方法

      // 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ù)定義在對(duì)象中 const obj = {   async fn() {       await f()   } }
      登錄后復(fù)制

      需要對(duì)這幾種情況進(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可以用以字符串形式的代碼來(lái)構(gòu)建AST樹(shù)節(jié)點(diǎn),快速優(yōu)雅開(kāi)發(fā)插件

      // 引入babel-template const template = require('babel-template');  // 定義try/catch語(yǔ)句模板 let tryTemplate = ` try { } catch (e) { console.log(CatchError:e) }`;  // 創(chuàng)建模板 const temp = template(tryTemplate);  // 給模版增加key,添加console.log打印信息 let tempArgumentObj = {    // 通過(guò)types.stringLiteral創(chuàng)建字符串字面量    CatchError: types.stringLiteral('Error') };  // 通過(guò)temp創(chuàng)建try語(yǔ)句的AST節(jié)點(diǎn) let tryNode = temp(tempArgumentObj);
      登錄后復(fù)制

      async函數(shù)體替換成try語(yǔ)句

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

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

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

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

      通過(guò)isTryStatement判斷是否已存在try語(yǔ)句

      module.exports = function (babel) {    let t = babel.type    return {       visitor: {        AwaitExpression(path) {                 // 判斷父路徑中是否已存在try語(yǔ)句,若存在直接返回         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 和方法名稱(chēng) funcName,方便快速定位問(wèn)題

      獲取文件路徑

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

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

      // 定義方法名 let asyncName = '';  // 獲取async節(jié)點(diǎn)的type類(lèi)型 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)來(lái)獲得同級(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ù)作為對(duì)象的方法,如vue項(xiàng)目中,在methods中定義的方法: methods: { async func() {} } case 'ObjectMethod':   asyncName = asyncPath.node.key.name || '';   break; }  // 若asyncName不存在,通過(guò)argument.callee獲取當(dāng)前執(zhí)行函數(shù)的name let funcName = asyncName || (node.argument.callee && node.argument.callee.name) || '';
      登錄后復(fù)制

      添加用戶(hù)選項(xiàng)

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

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

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

      customLog: 用戶(hù)自定義的打印信息

      最終代碼

      入口文件index.js

      // babel-template 用于將字符串形式的代碼來(lái)構(gòu)建AST樹(shù)節(jié)點(diǎn) const template = require('babel-template');  const { tryTemplate, catchConsole, mergeOptions, matchesFile } = require('./util');  module.exports = function (babel) {   // 通過(guò)babel 拿到 types 對(duì)象,操作 AST 節(jié)點(diǎn),比如創(chuàng)建、校驗(yàn)、轉(zhuǎn)變等   let types = babel.types;    // visitor:插件核心對(duì)象,定義了插件的工作流程,屬于訪問(wèn)者模式   const visitor = {     AwaitExpression(path) {       // 通過(guò)this.opts 獲取用戶(hù)的配置       if (this.opts && !typeof this.opts === 'object') {         return console.error('[babel-plugin-await-add-trycatch]: options need to be an object.');       }        // 判斷父路徑中是否已存在try語(yǔ)句,若存在直接返回       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á)式 || 對(duì)象的方法       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)來(lái)獲得同級(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ù)作為對(duì)象的方法,如vue項(xiàng)目中,在methods中定義的方法: methods: { async func() {} }         case 'ObjectMethod':           asyncName = asyncPath.node.key.name || '';           break;       }        // 若asyncName不存在,通過(guò)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 = {         // 通過(guò)types.stringLiteral創(chuàng)建字符串字面量         CatchError: types.stringLiteral(catchConsole(filePath, funcName, options.customLog))       };        // 通過(guò)temp創(chuàng)建try語(yǔ)句       let tryNode = temp(tempArgumentObj);        // 獲取async節(jié)點(diǎn)(父節(jié)點(diǎn))的函數(shù)體       let info = asyncPath.node.body;        // 將父節(jié)點(diǎn)原來(lái)的函數(shù)體放到try語(yǔ)句中       tryNode.block.body.push(...info.body);        // 將父節(jié)點(diǎn)的內(nèi)容替換成新創(chuàng)建的try語(yǔ)句       info.body = [tryNode];     }   };   return {     name: 'babel-plugin-await-add-trycatch',     visitor   }; };
      登錄后復(fù)制

      util.js

      const merge = require('deepmerge');  // 定義try語(yǔ)句模板 let tryTemplate = ` try { } catch (e) { console.log(CatchError,e) }`;  /*  * catch要打印的信息  * @param {string} filePath - 當(dāng)前執(zhí)行文件的路徑  * @param {string} funcName - 當(dāng)前執(zhí)行方法的名稱(chēng)  * @param {string} customLog - 用戶(hù)自定義的打印信息  */ 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倉(cāng)庫(kù)

      babel插件的安裝使用

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

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

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

      babel-plugin-await-add-trycatch

      總結(jié)

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

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