阅读更多

1顶
0踩

Web前端
引言
此次我们谈论的中间件,针对前端和Node的Express和Koa开发而言。对于严格意义上的中间件(平台与应用之间的通用服务),例如用于缓解后台高访问量的消息中间件,本篇不会去叙述,因为不是本篇的论述意图。

言归正传,当我们在编写业务代码时候,我们无法避免有些业务逻辑复杂而导致业务代码写得又长又乱,如果再加上时间紧凑情况下写出来的代码估计会更让人抓狂。以至于我们一直在寻求更好的架构设计和更好的代码设计,这是一个没有终点的求知之路,但是在这条路上会越走越好。

1. AOP
AOP意为面向切面编程,是在Java的Spring框架的重点内容,其作用如下图所示:

根据上图,整个响应http过程可以看做是一条串联的管道,对于每个http请求我们都想插入相同的逻辑例如数据过滤、日志统计的目的,为了不和业务逻辑混淆一块,提高代码复用率,AOP提倡从横向切面思路向管道某个位置插入一段代码逻辑,这样就实现在任何业务逻辑前后都有相同代码逻辑段,开发者只需专注写业务逻辑,既不影响整个响应http过程,而且隔离了业务逻辑,实现高内聚低耦合原则。

可以说AOP对OOP进行了一个补充,OOP是对做同一件事情的业务逻辑封装成一个对象,但是做一件事情过程中又想做别的事情对OOP来说难以解决。就像上图所示,当系统在响应用户修改信息的请求时,系统在业务处理之前对用户提交的数据做了安全过滤,业务处理之后还要做日志统计。相反如果把所有逻辑都柔合在一起,每次写业务都需重复编写数据过滤和日志统计的代码,违反了单一职责,高内聚低耦合的原则,并且降低代码复用率。

在前端,我们可以借用这种思想通过before和after函数来实现,我们看下代码实现:
Function.prototype.before = function(fn){//函数处理前执行fn
  var self = this;
   return function(){
     fn.call(this);
     self.apply(this, arguments);
   }
}

Function.prototype.after = function(fn){//函数处理后执行fn
  var self = this;
   return function(){
     self.apply(this, arguments);
     fn.call(this);
   }
}

实现思路是对被处理的函数通过闭包封装在新的函数里,在新的函数内部按照顺序执行传入的参数fn和被处理的函数。

举个栗子:

用户提交表单数据之前需要用户行为统计,代码应该是这样写:
function report(){
   console.log('上报数据');
}
function submit(){
   console.log('提交数据');
}

submit.before(report)(); //提交之前执行report
//结果: 上报数据
//      提交数据

从代码可以看出已经把统计和数据提交业务隔离起来,互不影响。

但是如果提交数据之前,需要数据验证并且依据验证结果判断是否能提交,怎么做?这里要改动before函数,看下代码:
Function.prototype.before = function(fn){//函数处理后执行fn
  var self = this;
   return function(){
     var res = fn.call(this);
     if(res)//返回成功则执行函数
       self.apply(this, arguments);
   }
}

function report(){
   console.log('上报数据');
   return true;
}
function validate(){
   console.log('验证不通过');
   return false;
}
function submit(){
   console.log('提交数据');
}

submit.before(report).before(validate)();
//结果: 
// 验证不通过 

function report(){
   console.log('上报数据');
   return true;
}
function validate(){
   console.log('验证通过');
   return true;
}
function submit(){
   console.log('提交数据');
}

submit.before(report).before(validate)();
//结果: 
// 验证通过
// 上报数据
// 提交数据

AOP思想在前端分解隔离业务已经做到位了,但是却有了一串长长的链式出来,如果处理不当很容易让维护者看晕,例如下面这样:
//提交数据前,验证数据,然后上报,在提交之后做返回首页的跳转
function report(){
   console.log('上报数据');
   return true;
}
function validate(){
   console.log('验证通过');
   return true;
}
function submit(){
   console.log('提交数据');
}
function goBack(){
   console.log('返回首页')
}
submit.before(report).before(validate).after(goBack)();
//结果: 
// 验证通过
// 上报数据
// 提交数据

栗子可能并没有那么晕,但是也得仔细看才能看懂整个流程,实际开发中估计会有更麻烦情况出现,另外,如果before或after的参数fn是一个异步操作的话,又需要做些patch,显然还是有些不足的,那么还有没有其他解决办法呢,既能隔离业务,又能方便清爽地使用~我们可以先看看其他框架的中间件解决方案。

2. express 与 koa的中间件
express和koa本身都是非常轻量的框架,express是集合路由和其他几个中间件合成的web开发框架,koa是express原班人马重新打造一个更轻量的框架,所以koa已经被剥离所有中间件,甚至连router中间件也被抽离出来,任由用户自行添加第三方中间件。express和koa中间件原理一样,我们就抽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只是一个方法用于返回请求。

3. 借用中间件
我们可以借用中间件思想来分解我们的前端业务逻辑,通过next方法层层传递给下一个业务。做到这几点首先必须有个管理中间件的对象,我们先创建一个名为Middleware的对象:
function Middleware(){
   this.cache = [];
}

Middleware通过数组缓存中间件。下面是next和use方法:
Middleware.prototype.use = function(fn){
  if(typeof fn !== 'function'){
    throw 'middleware must be a function';
  }
  this.cache.push(fn);
  return this;
}

Middleware.prototype.next = function(fn){
  if(this.middlewares && this.middlewares.length > 0 ){
    var ware = this.middlewares.shift();
    ware.call(this, this.next.bind(this));
  }
}
Middleware.prototype.handleRequest = function(){//执行请求
  this.middlewares = this.cache.map(function(fn){//复制
    return fn;
  });
  this.next();
}

我们用Middleware简单使用一下:
var middleware = new Middleware();
middleware.use(function(next){console.log(1);next();})
middleware.use(function(next){console.log(2);next();})
middleware.use(function(next){console.log(3);})
middleware.use(function(next){console.log(4);next();})
middleware.handleRequest();
//输出结果: 
//1
//2
//3
//4

没有出来是因为上一层中间件没有调用next方法,我们升级一下Middleware高级使用
var middleware = new Middleware();
middleware.use(function(next){
  console.log(1);next();console.log('1结束');
});
middleware.use(function(next){
   console.log(2);next();console.log('2结束');
});
middleware.use(function(next){
   console.log(3);console.log('3结束');
});
middleware.use(function(next){
   console.log(4);next();console.log('4结束');
});
middleware.handleRequest();
//输出结果: 
//1
//2
//3
//3结束
//2结束
//1结束

上面代码的流程图:

可以看出:每一个中间件执行权利传递给下一个中间件并等待其结束以后又回到当前并做别的事情,方法非常巧妙,有这特性读者可以玩转中间件。

4. 实际应用
/**
* @param data 验证的数据
* @param next 
*/
function validate(data, next){
  console.log('validate', data);//验证
  next();//通过验证
}

/**
* @param data 发送的数据
* @param next 
*/
function send(data, next){
   setTimeout(function(){//模拟异步
     console.log('send', data);//已发送数据
     next();
    }, 100);
}
function goTo(url, next){
   console.log('goTo', url);//跳转
}

validate和send函数都需要数据参数,目前Middleware只传next,需要传递data数据才能顺利执行下去,然而每个中间件需要的数据不一定都一致(就像goTo与validate、send)。

我们需要引入一个options对象来包裹这一串逻辑需要的数据,每个中间件在options内提取自己所需的数据,这样就能满足所有中间件,Middleware函数做相应调整:
function Middleware(){
  this.cache = [];
  this.options = null;//缓存options
}

Middleware.prototype.use = function(fn){
  if(typeof fn !== 'function'){
    throw 'middleware must be a function';
  }
  this.cache.push(fn);
  return this;
}

Middleware.prototype.next = function(fn){

  if(this.middlewares && this.middlewares.length > 0 ){
    var ware = this.middlewares.shift();
    ware.call(this, this.options, this.next.bind(this));//传入options与next
  }
}
/**
* @param options 数据的入口
* @param next 
*/
Middleware.prototype.handleRequest = function(options){
  this.middlewares = this.cache.map(function(fn){
    return fn;
  });
  this.options = options;//缓存数据
  this.next();
}

业务逻辑做相应修改:
function validate(options, next){
  console.log('validate', options.data);
  next();//通过验证
}
function send(options, next){
   setTimeout(function(){//模拟异步
     console.log('send', options.data);
     options.url = 'www.baidu.com';//设置跳转的url
     next();
    }, 100);
}
function goTo(options){
   console.log('goTo', options.url);
}

var submitForm = new Middleware();
submitForm.use(validate).use(send).use(goBack);
submitForm.handleRequest({data:{name:'xiaoxiong', age: 20}});
//结果:
// validate Object {name: "xiaoxiong", age: 20}
//
// send Object {name: "xiaoxiong", age: 20}
// goTo www.baidu.com


submitForm.handleRequest({data:{name:'xiaohong', age: 21}});//触发第二次,改变数据内容

//结果:
// validate Object {name: "xiaohong", age: 21}
//
// send Object {name: "xiaohong", age: 21}
// goTo www.baidu.com

以上代码大功告成。

5. 总结
通过以上代码,实现了业务隔离,满足每个业务所需的数据,又能很好控制业务下发执行的权利,所以“中间件”模式算是一种不错的设计。从代码阅读和代码编写的角度来说难度并不大,只要维护人员拥有该方面的知识,问题就不大了。

这里是完整代码:humyfred/js_demo_and_blog
  • 大小: 12.4 KB
  • 大小: 20.1 KB
  • 大小: 23.3 KB
来自: zhihu
1
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • Charon

    Charon

  • charon-spring-boot-starter:以Spring Boot启动器形式的反向代理实现

    Charon Spring启动启动器 Charon是反向代理实现。 它会自动将HTTP请求从一个HTTP服务器转发到另一个HTTP服务器,并将收到的HTTP响应发送回客户端。 有一些替代的反向代理实现,例如或 。 Zuul与紧密,Smiley的HTTP...

  • Charon-开源

    一个面向科学家和工程师的开源光流算法框架。

  • sakai charon分析文档

    sakai是广泛应用于高校的开源课程管理系统,具有良好的架构设计,具有很好的解耦行、扩展性和稳定性。目前sakai的二次开发广泛流行于企业、高校和个人,希望有兴趣我们可以共同交流,欢迎交流!

  • charon:授权和认证服务

    $ go install github.com/piotrkowalczuk/charon/cmd/charond $ go install github.com/piotrkowalczuk/charon/cmd/charonctl 超级用户 $ charonctl register -address=localhost:8080 -auth.disabled -register....

  • charon:用于组织暗物质通量计算的软件包

    χarον(charon) χarον是一个软件包,用于组织通过暗物质an灭/衰变计算中微子。 概述 用于检测由暗物质an灭/衰变产生的标准模型(SM)粒子的间接检测是当前寻找暗物质的重要方法。 来自天文学的稳定粒子是...

  • Go-Charon-授权和身份验证服务

    Charon - 授权和身份验证服务

  • Charon:用于路由 HTTP(S) 请求和转换响应的代理

    卡戎 用于路由 HTTP(S) 请求和转换响应的代理。

  • Charon 0.6

    代理服务器 检测 ,代理服务器 扫描 judge

  • 搜索大学文献代理 代理猎手 SmartWhois Charon

    共包含5个软件:代理猎手,Charon,angry ip scanner,SmartWhois,javasetup。以及操作步骤。还有自己使用中发现的小窍门。详见:说明文件。 本人已成功,搜到过代理,如:xx电子科技大学,xx交通大学。

  • charon:数字人文项目库

    Charon是一个数字框架,旨在为一系列贡献性和编辑性数字人文项目提供工作流管理。 Charon是CERES(致力于奖学金的社区增强知识库)的一部分,CERES是东北大学图书馆开发的一种较大的工具生态体系,可为高级形式的...

  • CHARON_VAX 最好的 alpha 模拟器

    CHARON_VAX 最好的 alpha 模拟器,总体使用最舒服的 alpha 模拟器了,欢迎大家下载学些

  • Charon Housing:英国RSL的开源住房系统-开源

    Charon Housing是一个开放源代码住房系统,被编写为在Charon环境下用作应用程序,并且针对英国的RSL(LA / HA / ALMO)。 Charon住房系统利用了Charon应用程序环境,该环境在其他地方都有介绍。 Charon Housing的两...

  • Charon:罪恶游戏社区的Discord机器人

    卡龙Charon是罪恶游戏协会社区的多功能Discord机器人。 如果您想检查一下或进行一些更改,请参见下文!安装确保在系统上安装了npm和node js 。 前往并创建一个应用程序。 将其命名为任意名称,然后转到左侧的bot标签...

  • charon-graphql:用于GraphQL客户端的轻量级缓存管理

    Charon是GraphQL的轻量级客户端缓存。 Charon提供了一个简单的API和一种可靠的方法来规范化和存储您的查询数据,因此数据不能存储两次。 这样可以更可靠地更新和处理缓存的数据。 安装 使用npm从命令行安装模块: ...

  • charon:地狱的渡轮(又名CAD版本控制)

    将此./charon/setup.sh添加为子模块,然后从Git Bash或MSYS2运行./charon/setup.sh。 然后,您可以照常使用git,但是git diff和git merge将添加糖。 如果您使用Linux,请检查 ,因为这两个项目已完全集成。 您还...

  • charon-unity3d:这是Unity3D的Charon游戏数据编辑器插件

    介绍Charon是用于管理游戏数据的软件。 它用于创建数据结构,编辑数据以及在代码中访问此数据。 对于Unity游戏引擎,可以使用编辑器扩展。 对于其他平台,应将独立版本与兼容的Web浏览器一起使用。基本 附加的功能...

  • lighthouse-charon:内部api,供应用程序从Vista检索数据

    Charon API 该Charon API为执行VistA RPC提供了简化的机制。 REST API使业务应用程序能够与VistA进行交互,而无需集成EJB技术。 Charon API还提供RPC广播,允许应用程序同时跨多个VistA实例调用RPC。 特别是,Charon...

  • strongswan--配置Charon-systemd问题解决

    今天尝试使用swanctl和systemd配置strongswan。配置方法见网址: ... https://wiki.strongswan.org/projects/strongswan/wiki/Charon-systemd ./configure --enable-systemd --enable-swanctl --disable-char...

  • setting.xml文件,修改Maven仓库指向至阿里仓

    setting.xml文件,修改Maven仓库指向至阿里仓

Global site tag (gtag.js) - Google Analytics