最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

如何在Express4.x中愉快地使用async的方法

 更新時間:2020年11月18日 09:59:49   作者:descire  
這篇文章主要介紹了如何在Express4.x中愉快地使用async的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

前言

為了能夠更好地處理異步流程,一般開發(fā)者會選擇 async 語法。在 express 框架中可以直接利用 async 來聲明中間件方法,但是對于該中間件的錯誤,無法通過錯誤捕獲中間件來劫持到。

錯誤處理中間件

 const express = require('express');
 const app = express();
 const PORT = process.env.PORT || 3000;

 app.get('/', (req, res) => {
  const message = doSomething();
  res.send(message);
 });

 // 錯誤處理中間件
 app.use(function (err, req, res, next) {
  return res.status(500).send('內(nèi)部錯誤!');
 });

 app.listen(PORT, () => console.log(`app listening on port ${PORT}`));

以上述代碼為例,中間件方法并沒有通過 async 語法來聲明,如果 doSomething 方法內(nèi)部拋出異常,那么就可以在錯誤處理中間件中捕獲到錯誤,從而進行相應地異常處理。

 app.get('/', async (req, res) => {
  const message = doSomething();
  res.send(message);
 });

而采用 async 語法來聲明中間件時,一旦 doSomething 內(nèi)部拋出異常,則錯誤處理中間件無法捕獲到。

雖然可以利用 process 監(jiān)聽 unhandledRejection 事件來捕獲,但是無法正確地處理后續(xù)流程。

try/catch

對于 async 聲明的函數(shù),可以通過 try/catch 來捕獲其內(nèi)部的錯誤,再使用 next 函數(shù)將錯誤遞交給錯誤處理中間件,即可處理該場景:

 app.get('/', async (req, res, next) => {
  try {
   const message = doSomething();
   res.send(message);
  } catch(err) {
   next(err);
  }
 });

「 這種寫法簡單易懂,但是滿屏的 try/catch 語法,會顯得非常繁瑣且不優(yōu)雅。 」

高階函數(shù)

對于基礎扎實的開發(fā)來說,都知道 async 函數(shù)最終返回一個 Promise 對象,而對于 Promsie 對象應該利用其提供的 catch 方法來捕獲異常。

那么在將 async 語法聲明的中間件方法傳入 use 之前,需要包裹一層 Promise 函數(shù)的異常處理邏輯,這時就需要利用高階函數(shù)來完成這樣的操作。

 function asyncUtil(fn) {
  return function asyncUtilWrap(...args) {
   const fnReturn = fn(args);
   const next = args[args.length - 1];
   return Promise.resolve(fnReturn).catch(next);
  }
 }

 app.use(asyncUtil(async (req, res, next) => {
  const message = doSomething();
  res.send(message);
 }));

相比較第一種方法, 「 高階函數(shù)減少了冗余代碼,在一定程度上提高了代碼的可讀性。

上述兩種方案基于扎實的 JavaScript 基礎以及 Express 框架的熟練使用,接下來從源碼的角度思考合適的解決方案。

中間件機制

Express 中主要包含三種中間件:

  • 應用級別中間件
  • 路由級別中間件
  • 錯誤處理中間件
app.use = function use(fn) {
 var path = '/';

 // 省略參數(shù)處理邏輯
 ...

 // 初始化內(nèi)置中間件
 this.lazyrouter();
 var router = this._router;

 fns.forEach(function (fn) {
  // non-express app
  if (!fn || !fn.handle || !fn.set) {
   return router.use(path, fn);
  }

  ...

 }, this);

 return this;
};

應用級別中間件通過 app.use 方法注冊, 「 其本質上也是調用路由對象上的中間件注冊方法,只不過其默認路由為 '/' 」 。

proto.use = function use(fn) {
 var offset = 0;
 var path = '/';

 // 省略參數(shù)處理邏輯
 ...

 var callbacks = flatten(slice.call(arguments, offset));

 for (var i = 0; i < callbacks.length; i++) {
  var fn = callbacks[i];

  ...

  // add the middleware
  debug('use %o %s', path, fn.name || '<anonymous>')

  var layer = new Layer(path, {
   sensitive: this.caseSensitive,
   strict: false,
   end: false
  }, fn);

  layer.route = undefined;

  this.stack.push(layer);
 }

 return this;
};

中間件的所有注冊方式最終會調用上述代碼,根據(jù) path 和中間件處理函數(shù)生成 layer 實例,再通過棧來維護這些 layer 實例。

// 部分核心代碼
proto.handle = function handle(req, res, out) {
 var self = this;
 var idx = 0;
 var stack = self.stack;

 next();

 function next(err) {
  var layerError = err === 'route'
   ? null
   : err;
  
  if (idx >= stack.length) {
   return;
  }

  var path = getPathname(req);

  // find next matching layer
  var layer;
  var match;
  var route;

  while (match !== true && idx < stack.length) {
   layer = stack[idx++];
   match = matchLayer(layer, path);
   route = layer.route;

   if (match !== true) {
    continue;
   }

  }

  // no match
  if (match !== true) {
   return done(layerError);
  }

  // this should be done for the layer
  self.process_params(layer, paramcalled, req, res, function (err) {
   if (err) {
    return next(layerError || err);
   }

   if (route) {
    return layer.handle_request(req, res, next);
   }

   trim_prefix(layer, layerError, layerPath, path);
  });
 }

 function trim_prefix(layer, layerError, layerPath, path) {

  if (layerError) {
   layer.handle_error(layerError, req, res, next);
  } else {
   layer.handle_request(req, res, next);
  }
 }
};

Express 內(nèi)部通過 handle 方法來處理中間件執(zhí)行邏輯,其利用 「 閉包的特性 」 緩存 idx 來記錄當前遍歷的狀態(tài)。

該方法內(nèi)部又實現(xiàn)了 next 方法來匹配當前需要執(zhí)行的中間件,從遍歷的代碼可以明白 「 中間件注冊的順序是非常重要的 」 。

如果該流程存在異常,則調用 layer 實例的 handle.error 方法,這里仍然是 「 遵循了 Node.js 錯誤優(yōu)先的設計理念 」 :

Layer.prototype.handle_error = function handle_error(error, req, res, next) {
 var fn = this.handle;

 if (fn.length !== 4) {
  // not a standard error handler
  return next(error);
 }

 try {
  fn(error, req, res, next);
 } catch (err) {
  next(err);
 }
};

內(nèi)部通過判斷函數(shù)的形參個數(shù)過濾掉非錯誤處理中間件」。
如果 next 函數(shù)內(nèi)部沒有異常情況,則調用 layer 實例的 handle_request 方法:

Layer.prototype.handle_request = function handle(req, res, next) {
 var fn = this.handle;

 if (fn.length > 3) {
  // not a standard request handler
  return next();
 }

 try {
  fn(req, res, next);
 } catch (err) {
  next(err);
 }
};

 「 handle 方法初始化執(zhí)行了一次 next 方法,但是該方法每次調用最多只能匹配一個中間件 」 ,所以在執(zhí)行 handle_error 和 handle_request 方法時,會將 next 方法透傳給中間件,這樣開發(fā)者就可以通過手動調用 next 方法的方式來執(zhí)行接下來的中間件。

從上述中間件的執(zhí)行流程中可以知曉, 「 用戶注冊的中間件方法在執(zhí)行的時候都會包裹一層 try/catch,但是 try/catch 無法捕獲 async 函數(shù)內(nèi)部的異常,這也就是為什么 Express 中無法通過注冊錯誤處理中間件來攔截到 async 語法聲明的中間件的異常的原因 」 。

修改源碼

找到本質原因之后,可以通過修改源碼的方法來進行適配:

Layer.prototype.handle_request = function handle(req, res, next) {
 var fn = this.handle;

 if (fn.length > 3) {
  // not a standard request handler
  return next();
 }
 // 針對 async 語法函數(shù)特殊處理
 if (Object.prototype.toString.call(fn) === '[object AsyncFunction]') {
  return fn(req, res, next).catch(next);
 }

 try {
  fn(req, res, next);
 } catch (err) {
  next(err);
 }
};

上述代碼在 handle_request 方法內(nèi)部判斷了中間件方法通過 async 語法聲明的情況,從而采用 Promise 對象的 catch 方法來向下傳遞異常。

這種方式可以減少上層冗余的代碼,但是實現(xiàn)該方式,可能需要 fork 一份 Express4.x 的源碼,然后發(fā)布一個修改之后的版本,后續(xù)還要跟進官方版本的新特性,相應的維護成本非常高。

express5.x 中將 router 部分剝離出了單獨的路由庫 -- router

AOP(面向切面編程)

為了解決上述方案存在的問題,我們可以嘗試利用 AOP 技術在不修改源碼的基礎上對已有方法進行增強。

app.use(async function () {
 const message = doSomething();
 res.send(message);
})

以注冊應用級別中間件為例,可以對 app.use 方法進行 AOP 增強:

const originAppUseMethod = app.use.bind(app);
app.use = function (fn) {
 if (Object.prototype.toString.call(fn) === '[object AsyncFunction]') {
  const asyncWrapper = function(req, res, next) {
   fn(req, res, next).then(next).catch(next);
  }
  return originAppUseMethod(asyncWrapper);
 }
 return originAppUseMethod(fn);
}

前面源碼分析的過程中,app.use 內(nèi)部是有 this 調用的,所以這里需要 「 利用 bind 方法來避免后續(xù)調用過程中 this 指向出現(xiàn)問題。

然后就是利用 AOP 的核心思想,重寫原始的 app.use 方法,通過不同的分支邏輯代理到原始的 app.use 方法上。

該方法相比較修改源碼的方式,維護成本低。但是缺點也很明顯,需要重寫所有可以注冊中間件的方法,不能夠像修改源碼那樣一步到位。

寫在最后

本文介紹了 Express 中使用 async 語法的四種解決方案:

  • try/catch
  • 高階函數(shù)
  • 修改源碼
  • AOP

除了 try/catch 方法性價比比較低,其它三種方法都需要根據(jù)實際情況去取舍,舉個栗子:

如果你需要寫一個 Express 中間件提供給各個團隊使用,那么修改源碼的方式肯定走不通,而 AOP 的方式對于你的風險太大,相比較下,第二種方案是最佳的實踐方案。

到此這篇關于如何在Express4.x中愉快地使用async的方法的文章就介紹到這了,更多相關Express4.x使用async內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關文章

  • 從零開始學習Node.js系列教程六:EventEmitter發(fā)送和接收事件的方法示例

    從零開始學習Node.js系列教程六:EventEmitter發(fā)送和接收事件的方法示例

    這篇文章主要介紹了Node.js EventEmitter發(fā)送和接收事件的方法,結合實例形式分析了EventEmitter發(fā)送和接收事件的原理、實現(xiàn)方法與相關操作技巧,需要的朋友可以參考下
    2017-04-04
  • Sublime Text3 配置 NodeJs 環(huán)境的方法

    Sublime Text3 配置 NodeJs 環(huán)境的方法

    大家都知道,Sublime Text 安裝插件一般從 Package Control 中直接安裝即可,當我安裝 node js 插件時候,直接通過Package Control 安裝,雖然插件安裝成功了,但是找不到配置文件 Nodejs.sublime-build 來更改一些配置
    2020-05-05
  • 用C/C++來實現(xiàn) Node.js 的模塊(一)

    用C/C++來實現(xiàn) Node.js 的模塊(一)

    這篇文章的主要內(nèi)容其實簡而言之就是——用C/C++來實現(xiàn) Node.js 的模塊,非常的不錯,有需要的朋友可以參考下
    2014-09-09
  • node.js中的fs.exists方法使用說明

    node.js中的fs.exists方法使用說明

    這篇文章主要介紹了node.js中的fs.exists方法使用說明,本文介紹了fs.exists方法說明、語法、接收參數(shù)、使用實例和實現(xiàn)源碼,需要的朋友可以參考下
    2014-12-12
  • node.js中的fs.read方法使用說明

    node.js中的fs.read方法使用說明

    這篇文章主要介紹了node.js中的fs.read方法使用說明,本文介紹了fs.read方法說明、語法、接收參數(shù)、使用實例和實現(xiàn)源碼,需要的朋友可以參考下
    2014-12-12
  • node-sass安裝失敗的原因與解決方法

    node-sass安裝失敗的原因與解決方法

    這篇文章主要給大家介紹了關于node-sass安裝失敗的原因與解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。
    2017-09-09
  • NodeJS服務器實現(xiàn)gzip壓縮的示例代碼

    NodeJS服務器實現(xiàn)gzip壓縮的示例代碼

    這篇文章主要介紹了NodeJS服務器實現(xiàn)gzip壓縮的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-10-10
  • nodejs+mysql實現(xiàn)用戶相關的增刪改查的詳細操作

    nodejs+mysql實現(xiàn)用戶相關的增刪改查的詳細操作

    這篇文章主要介紹了nodejs+mysql實現(xiàn)用戶相關的增刪改查的詳細操作的相關資料,需要的朋友可以參考下
    2023-05-05
  • 利用node.js開發(fā)cli的完整步驟

    利用node.js開發(fā)cli的完整步驟

    這篇文章主要給大家介紹了關于如何利用node.js開發(fā)cli的完整步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-12-12
  • Linux系統(tǒng)中如何下載、解壓和安裝特定版本的Node.js

    Linux系統(tǒng)中如何下載、解壓和安裝特定版本的Node.js

    Nodejs版本坑眾多,不同應用可能需要不同版本,下面這篇文章主要給大家介紹了關于Linux系統(tǒng)中如何下載、解壓和安裝特定版本的Node.js的相關資料,需要的朋友可以參考下
    2024-01-01

最新評論

遵义县| 启东市| 桑植县| 勐海县| 科技| 米泉市| 陕西省| 高密市| 孝义市| 肇庆市| 寿阳县| 孝感市| 贡觉县| 海阳市| 乡城县| 临安市| 盐源县| 门源| 舞阳县| 资阳市| 河间市| 龙岩市| 阳新县| 鲁山县| 琼结县| 剑川县| 轮台县| 墨竹工卡县| 邮箱| 和田县| 南涧| 兴宁市| 固始县| 射洪县| 宁都县| 七台河市| 海盐县| 商丘市| 柘荣县| 河东区| 乐至县|