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

淺談Node.js 中間件模式

 更新時間:2018年06月12日 13:44:58   作者:雞腿  
中間件在 Node.js 中被廣泛使用,它泛指一種特定的設計模式、一系列的處理單元、過濾器和處理程序,以函數(shù)的形式存在,這篇文章主要介紹了淺談Node.js 中間件模式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

中間件在 Node.js 中被廣泛使用,它泛指一種特定的設計模式、一系列的處理單元、過濾器和處理程序,以函數(shù)的形式存在,連接在一起,形成一個異步隊列,來完成對任何數(shù)據(jù)的預處理和后處理。

它的優(yōu)點在于 靈活性 :使用中間件我們用極少的操作就能得到一個插件,用最簡單的方法就能將新的過濾器和處理程序擴展到現(xiàn)有的系統(tǒng)上。

常規(guī)中間件模式

中間件模式中,最基礎的組成部分就是 中間件管理器 ,我們可以用它來組織和執(zhí)行中間件的函數(shù),如圖所示:

要實現(xiàn)中間件模式,最重要的實現(xiàn)細節(jié)是:

  1. 可以通過調用use()函數(shù)來注冊新的中間件,通常,新的中間件只能被添加到高壓包帶的末端,但不是嚴格要求這么做;
  2. 當接收到需要處理的新數(shù)據(jù)時,注冊的中間件在意不執(zhí)行流程中被依次調用。每個中間件都接受上一個中間件的執(zhí)行結果作為輸入值;
  3. 每個中間件都可以停止數(shù)據(jù)的進一步處理,只需要簡單地不調用它的毀掉函數(shù)或者將錯誤傳遞給回調函數(shù)。當發(fā)生錯誤時,通常會觸發(fā)執(zhí)行另一個專門處理錯誤的中間件。

至于怎么處理傳遞數(shù)據(jù),目前沒有嚴格的規(guī)則,一般有幾種方式

  1. 通過添加屬性和方法來增強;
  2. 使用某種處理的結果來替換 data;
  3. 保證原始要處理的數(shù)據(jù)不變,永遠返回新的副本作為處理的結果。

而具體的處理方式取決于 中間件管理器 的實現(xiàn)方式以及中間件本身要完成的任務類型。

舉一個來自于 《Node.js 設計模式 第二版》 的一個為消息傳遞庫實現(xiàn) 中間件管理器 的例子:

class ZmqMiddlewareManager {
 constructor(socket) {
  this.socket = socket;
  // 兩個列表分別保存兩類中間件函數(shù):接受到的信息和發(fā)送的信息。
  this.inboundMiddleware = [];
  this.outboundMiddleware = [];
  socket.on('message', message => {
   this.executeMiddleware(this.inboundMiddleware, {
    data: message
   });
  });
 }
 
 send(data) {
  const message = { data };
  
  this.excuteMiddleware(this.outboundMiddleware, message, () => {
   this.socket.send(message.data);
  });
 }
 
 use(middleware) {
  if(middleware.inbound) {
   this.inboundMiddleware.push(middleware.inbound);
  }
  if(middleware.outbound) {
   this.outboundMiddleware.push(middleware.outbound);
  }
 }
 
 exucuteMiddleware(middleware, arg, finish) {
  function iterator(index) {
   if(index === middleware.length) {
    return finish && finish();
   }
   middleware[index].call(this, arg, err => {
    if(err) {
     return console.log('There was an error: ' + err.message);
    }
    iterator.call(this, ++index);
   });
  }
  iterator.call(this, 0);
 }
}

接下來只需要創(chuàng)建中間件,分別在 inbound 和 outbound 中寫入中間件函數(shù),然后執(zhí)行完畢調用 next() 就好了。比如: 

const zmqm = new ZmqMiddlewareManager();

zmqm.use({
 inbound: function(message, next) {
  console.log('input message: ', message.data);
  next();
 },
 outbound: function(message, next) {
  console.log('output message: ', message.data);
  next();
 }
});

Express 所推廣的 中間件 概念就與之類似,一個 Express 中間件一般是這樣的:

function(req, res, next) { ... }

Koa2 中使用的中間件

前面展示的中間件模型使用回調函數(shù)實現(xiàn)的,但是現(xiàn)在有一個比較時髦的 Node.js 框架 Koa2 的中間件實現(xiàn)方式與之前描述的有一些不太相同。 Koa2 中的中間件模式移除了一開始使用 ES2015 中的生成器實現(xiàn)的方法,兼容了回調函數(shù)、 convert 后的生成器以及 async 和 await 。

在 Koa2 官方文檔中給出了一個關于中間件的 洋蔥模型 ,如下圖所示:

從圖中我們可以看到,先進入 inbound 的中間件函數(shù)在 outbound 中被放到了后面執(zhí)行,那么究竟是為什么呢?帶著這個問題我們去讀一下 Koa2 的源碼。

在 koa/lib/applications.js 中,先看構造函數(shù),其它的都可以不管,關鍵就是 this.middleware ,它是一個 inbound 隊列:

constructor() {
 super();

 this.proxy = false;
 this.middleware = [];
 this.subdomainOffset = 2;
 this.env = process.env.NODE_ENV || 'development';
 this.context = Object.create(context);
 this.request = Object.create(request);
 this.response = Object.create(response);
}

和上面一樣,在 Koa2 中也是用 use() 來把中間件放入隊列中:

use(fn) {
 if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
 if (isGeneratorFunction(fn)) {
  deprecate('Support for generators will be removed in v3. ' +
    'See the documentation for examples of how to convert old middleware ' +
    'https://github.com/koajs/koa/blob/master/docs/migration.md');
  fn = convert(fn);
 }
 debug('use %s', fn._name || fn.name || '-');
 this.middleware.push(fn);
 return this;
}

接著我們看框架對端口監(jiān)聽進行了一個簡單的封裝:

// 封裝之前 http.createServer(app.callback()).listen(...)
listen(...args) {
 debug('listen');
 const server = http.createServer(this.callback());
 return server.listen(...args);
}

中間件的管理關鍵就在于 this.callback() ,看一下這個方法:

callback() {
 const fn = compose(this.middleware);
 
 if (!this.listenerCount('error')) this.on('error', this.onerror);
 
 const handleRequest = (req, res) => {
  const ctx = this.createContext(req, res);
  return this.handleRequest(ctx, fn);
 };
 
 return handleRequest;
}

這里的 compose 方法實際上是 Koa2 的一個核心模塊 koa-compose (https://github.com/koajs/compose),在這個模塊中封裝了中間件執(zhí)行的方法:

function compose (middleware) {
 if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
 for (const fn of middleware) {
  if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
 }
 
  /**
  * @param {Object} context
  * @return {Promise}
  * @api public
  */
 
 return function (context, next) {
  // last called middleware #
  let index = -1
  return dispatch(0)
  function dispatch (i) {
   if (i <= index) return Promise.reject(new Error('next() called multiple times'))
   index = i
   let fn = middleware[i]
   if (i === middleware.length) fn = next
   if (!fn) return Promise.resolve()
   try {
    return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
   } catch (err) {
    return Promise.reject(err)
   }
  }
 }
}

可以看到, compose 通過遞歸對中間件隊列進行了 反序遍歷 ,生成了一個 Promise 鏈,接下來,只需要調用 Promise 就可以執(zhí)行中間件函數(shù)了:

handleRequest(ctx, fnMiddleware) {
 const res = ctx.res;
 res.statusCode = 404;
 const onerror = err => ctx.onerror(err);
 const handleResponse = () => respond(ctx);
 onFinished(res, onerror);
 return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}

從源碼中可以發(fā)現(xiàn), next() 中返回的是一個 Promise ,所以通用的中間件寫法是:

app.use((ctx, next) => {
 const start = new Date();
 return next().then(() => {
  const ms = new Date() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
 });
});

當然如果要用 async 和 await 也行:

app.use((ctx, next) => {
 const start = new Date();
 await next();
 const ms = new Date() - start;
 console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

由于還有很多 Koa1 的項目中間件是基于生成器的,需要使用 koa-convert 來進行平滑升級:

const convert = require('koa-convert');

app.use(convert(function *(next) {
 const start = new Date();
 yield next;
 const ms = new Date() - start;
 console.log(`${this.method} ${this.url} - ${ms}ms`);
}));

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

最新評論

根河市| 株洲县| 阳城县| 石林| 汨罗市| 荔浦县| 祁阳县| 柘荣县| 石渠县| 绍兴县| 秦皇岛市| 麟游县| 平远县| 富蕴县| 衢州市| 六枝特区| 克什克腾旗| 北川| 砚山县| 马山县| 崇州市| 鸡东县| 甘洛县| 铁岭市| 贡觉县| 宜良县| 手游| 岢岚县| 龙门县| 中超| 萨嘎县| 铜川市| 洛浦县| 库车县| 温宿县| 曲阳县| 伊金霍洛旗| 若尔盖县| 苏尼特左旗| 关岭| 安新县|