實際場景
在日常的開發過程中,我們在編寫業務代碼時候,無法避免有些業務邏輯復雜而導致業務代碼寫得又長又亂。有些邏輯像一個過程,在不同的節點需要做不同的操作。
比如,我們在開發的過程中經常會遇到數據提交這樣一個場景。我們的目的是數據提交,但是在提交之前,我們需要對數據進行驗證,驗證正確之后,對數據發送進行上報,上報之后才是我們的目標操作提交數據。提交數據之后我們還需要跳轉到提交成功的頁面。這時候我們一般的做法會是這樣:
if(//驗證數據){
//上報數據操作
//提交數據操作
//跳轉成功頁面
}
這時候我們會將整個流程的代碼糅合在一起,如果代碼簡單一點還好,但是如果每一個步驟都有大量的邏輯操作,估計會讓人抓狂。
解決方案
對於這一類的流程事件,我們可以采用分解這些事件,當需要用到這些事件操作時,我們將操作插入到核心事件完成所需要的不同步驟中。我們通過下面的方式來實現提交的功能:
Function.prototype.before = function(fn){ var self = this; return function(){ var res = fn.call(this); if(res){ self.call(this,arguments); } } }; Function.prototype.after = function(fn){ var self = this; return function(){ self.call(this,arguments); fn.call(this); } }; function report(){ console.log('上報數據'); return true; } function validate(){ console.log('驗證數據'); if( + new Date()%2 == 0){ return true; }else{ return false; } } function submit(){ console.log('提交數據'); } function goback(){ console.log('返回首頁'); } submit.before(report).before(validate).after(goback)();
通過上面的代碼,我們將各個階段的業務給分解開來,這樣做的好處很明顯,我們只要關注各個階段的代碼實現,最后將各個階段通過管道式的方式拼裝起來。有利於我們代碼邏輯的解耦符合我們高內聚低耦合的原則。同時,各部分的代碼又獨立存在,當其他業務邏輯需要用到的時候,我們只需要把需要的部分取出來,拼裝在需要的邏輯上面就可以了。這又有利於代碼的復用。
但是,上面的代碼又有兩個問題
1、一串長長的鏈式調用,不方便維護者理解
2、如何before或者after的參數是一個異步操作的話,又需要做一些patch
有沒有其他的方法來實現既能隔離業務,又能方便地使用呢。我們來看express的實現方式
Express中間件的實現
我們來看express的實現方法
var express = require('express'); var app = express(); app.use(function(req, res, next) { console.log('數據統計'); next();//執行權利傳遞給 }); app.use(function(req, res, next) { console.log('日志統計'); next(); }); app.get('/', function(req, res, next) { res.send('Hello World!'); }); app.listen(3000); //整個請求處理過程就是先數據統計、日志統計,最后返回一個Hello World!
上圖的運作流程
從上圖來看,每一個“管道”都是一個中間件,每個中間件通過next方法傳遞執行權給下一個中間件,express就是一個收集並調用各種中間件的容器。
中間件就是一個函數,通過express的use方法接收中間件,每個中間件有express傳入的req,res和next參數。如果要把請求傳遞給下一個中間件必須使用 next() 方法。當調用res.send方法則此次請求結束,node直接返回請求給客戶,但是若在res.send方法之后調用next方法,整個中間件鏈式調用還會往下執行,因為當前hello world所處的函數也是一塊中間件,而res.send只是一個方法用於返回請求。
借用中間件實現
我們可以借用中間件思想來分解我們的前端業務邏輯,通過next方法層層傳遞給下一個業務。
代碼如下:
var MidWare = function(){ this.cache = []; this.options = {} } MidWare.prototype.use = function(fn){ if(typeof fn !== 'function'){ console.log('need a function'); return false; } this.cache.push(fn); return this; } MidWare.prototype.next = function(argument){ if(this.midwares && this.midwares.length > 0){ var ware = this.midwares.shift(); ware.call(this,this.options || {}, this.next.bind(this)) } }; MidWare.prototype.handleRequest = function(options){ this.midwares = this.cache.map(function(fn){ return fn; }); this.options = options;//緩存數據 this.next(); } var submitForm = new MidWare(); //驗證 submitForm.use(function(options, next){ console.log('驗證數據'); next(); }) //上報 submitForm.use(function(options, next){ setTimeout(function(){ console.log('上報數據'); next(); }, 3000) }) //提交數據 submitForm.use(function(options, next){ console.log('提交數據'); next(); }) //返回首頁 submitForm.use(function(options, next){ console.log('返回首頁'); }) submitForm.handleRequest();