实际场景
在日常的开发过程中,我们在编写业务代码时候,无法避免有些业务逻辑复杂而导致业务代码写得又长又乱。有些逻辑像一个过程,在不同的节点需要做不同的操作。
比如,我们在开发的过程中经常会遇到数据提交这样一个场景。我们的目的是数据提交,但是在提交之前,我们需要对数据进行验证,验证正确之后,对数据发送进行上报,上报之后才是我们的目标操作提交数据。提交数据之后我们还需要跳转到提交成功的页面。这时候我们一般的做法会是这样:
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();