博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
中间件(middleware)
阅读量:6238 次
发布时间:2019-06-22

本文共 9287 字,大约阅读时间需要 30 分钟。

介绍

在我们日常开发中,越来越多看到了中间件这个词,例如Koa,redux等。这里就大概记录一下Koa和redux中间件的实现方式,可以从中看到中间件的实现方式都是大同小异,基本都是实现了洋葱模型。

对于中间件我们需要了解的是

  • 中间件是如何存储的
  • 中间件是如何执行的

正文

Koa

作为TJ大神的作品,真不愧是号称基于 Node.js 平台的下一代 web 开发框架,其中对于中间件的实现,generator/yield,还是await/async,对于回调地狱的处理,都是给后来的开发者很大的影响。

Koa 1的中间件

存储
/** * https://github.com/Koajs/Koa/blob/1.6.0/lib/application.js */ ... var app = Application.prototype; function Application() {  if (!(this instanceof Application)) return new Application;  this.env = process.env.NODE_ENV || 'development';  this.subdomainOffset = 2;  this.middleware = [];  this.proxy = false;  this.context = Object.create(context);  this.request = Object.create(request);  this.response = Object.create(response);}...app.use = function(fn){  if (!this.experimental) {    // es7 async functions are not allowed,    // so we have to make sure that `fn` is a generator function    assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function');  }  debug('use %s', fn._name || fn.name || '-');  this.middleware.push(fn);  return this;};复制代码

可以在这里看到我们通过app.use加入的中间件,保存在一个middleware的数组中。

执行
/** * https://github.com/Koajs/Koa/blob/1.6.0/lib/application.js */app.listen = function(){  debug('listen');  var server = http.createServer(this.callback());  return server.listen.apply(server, arguments);};// 删除了一些警告代码app.callback = function(){  ...  var fn = this.experimental    ? compose_es7(this.middleware)    : co.wrap(compose(this.middleware));  var self = this;  ...  return function handleRequest(req, res){    var ctx = self.createContext(req, res);    self.handleRequest(ctx, fn);  }};app.handleRequest = function(ctx, fnMiddleware){  ctx.res.statusCode = 404;  onFinished(ctx.res, ctx.onerror);  fnMiddleware.call(ctx).then(function handleResponse() {    respond.call(ctx);  }).catch(ctx.onerror);};复制代码

可以在这里看到middleware数组经过一些处理,生成了fn,然后通过fnMiddleware.call(ctx)传入ctx来处理,然后就将ctx传给了respond,所以这里的fnMiddleware就是我们需要去了解的内容。

这里首先判断是否是this.experimental来获取是否使用了async/await,这个我们在Koa1中不做详细介绍。我们主要是来看一下co.wrap(compose(this.middleware))

让我们先来看一下compose()

/** * 这里使用了Koa1@1.6.0 package.json中的Koa-compose的版本 * https://github.com/Koajs/compose/blob/2.3.0/index.js */function compose(middleware){  return function *(next){    if (!next) next = noop();    var i = middleware.length;    while (i--) {      next = middleware[i].call(this, next);    }    return yield *next;  }}function *noop(){}复制代码

co.wrap(compose(this.middleware))就变成了如下的样子

co.wrap(function *(next){    if (!next) next = noop();    var i = middleware.length;    while (i--) {      next = middleware[i].call(this, next);    }    return yield *next;})复制代码

我们可以看到这里对middleware进行了倒序遍历。next = middleware[i].call(this, next);可以写为类似下面这个代码结构

function *middleware1() {  ...  yield function *next1() {    ...	yield function *next2() {	  ...	  ...	  ...	}	...  }  ...}复制代码

然后next = middleware[i].call(this, next);其实每一个next就是一个middleware,所以也就可以变成

function *middleware1() {  ...  yield function *middleware2() {    ...    yield function *middleware() {      ...	  ...	  ...	}	...  }  ...}复制代码

然后我们就获得了下面这个代码

co.wrap(function *(next){  next = function *middleware1() {    ...    yield function *middleware2() {      ...      yield (function *middleware3() {        ...        yield function *() {          // noop          // NO next yield !        }        ...      }      ...    }    ...  }  return yield *next;})复制代码

至此我们来看一眼洋葱模型, 是不是和我们上面的代码结构很想。

现在我们有了洋葱模型式的中间节代码,接下来就是执行它。接下来就是co.wrap,这里我们就不详细说明了,co框架就是一个通过Promise来让generator自执行的框架,实现了类似async/await的功能(其实应该说async/await的实现方式就是Promisegenerator)。

这里提一个最后yield *next,是让code可以少执行一些,因为如果使用yield next,会返回一个迭代器,然后co来执行这个迭代器,而yield *则是相当于将generator里面的内容写在当前函数中,详细可以见

关于Koa1可以看我的早一些写的另一篇

Koa 2的中间件

存储
/** * https://github.com/Koajs/Koa/blob/1.6.0/lib/application.js */ ... 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);  }...  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;  }复制代码

Koa2对于middleware的存储和Koa1基本一模一样,保存在一个数组中。

执行
callback() {    const fn = compose(this.middleware);    if (!this.listeners('error').length) this.on('error', this.onerror);    const handleRequest = (req, res) => {      const ctx = this.createContext(req, res);      return this.handleRequest(ctx, fn);    };    return handleRequest;  }  /**   * Handle request in callback.   *   * @api private   */  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);复制代码

这里主要就是两行代码

const fn = compose(this.middleware);// fnMiddleware === fnfnMiddleware(ctx).then(handleResponse).catch(onerror);复制代码

Koa2的代码似乎比Koa1要简介一些了,在默认使用await/async之后,少了co的使用。

fnMiddleware(ctx).then(handleResponse).catch(onerror);我们可以知道fnMiddleware返回了一个Promise,然后执行了这个Promise,所以我们主要知道compose做了什么就好。

/** * https://github.com/Koajs/compose/blob/4.0.0/index.js */function compose (middleware) {  ...  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, function next () {          return dispatch(i + 1)        }))      } catch (err) {        return Promise.reject(err)      }    }  }}复制代码

看起来这段代码比Koa1compose稍微复杂了些,其实差不多,主要的代码其实也就两个

function compose (middleware) {  ...  return function (context, next) {    let index = -1    return dispatch(0)    function dispatch (i) {      let fn = middleware[i]      return Promise.resolve(fn(context, function next () {        return dispatch(i + 1)      }))    }  }}复制代码

相比于Koa1遍历middleware数组,Koa2改为了递归。同上面一样,我们可以将函数写为如下结构

async function middleware1() {  ...  await (async function middleware2() {    ...    await (async function middleware3() {      ...    });    ...  });  ...}复制代码

因为async函数的自执行,所以直接运行该函数就可以了。

可以看到Koa1Koa2的中间件的实现方式基本是一样的,只是一个是基于generator/yield, 一个是基于async/await

Redux

相比于Koa的中间件的具体实现,Redux相对稍复杂一些。

本人对于Redux基本没有使用,只是写过一些简单的demo,看过一部分的源码,如有错误,请指正

存储

我们在使用Redux的时候可能会这么写

// 好高阶的函数啊const logger = store => next => action => {  console.group(action.type)  console.info('dispatching', action)  let result = next(action)  console.log('next state', store.getState())  console.groupEnd(action.type)  return result}let store = createStore(  todoApp,  applyMiddleware(    logger  ))复制代码

我们可以很方便的找到applyMiddleware的源码。

export default function applyMiddleware(...middlewares) {  return createStore => (...args) => {    const store = createStore(...args)    let dispatch = () => {      throw new Error(        `Dispatching while constructing your middleware is not allowed. ` +          `Other middleware would not be applied to this dispatch.`      )    }    const middlewareAPI = {      getState: store.getState,      dispatch: (...args) => dispatch(...args)    }    const chain = middlewares.map(middleware => middleware(middlewareAPI))    dispatch = compose(...chain)(store.dispatch)    return {      ...store,      dispatch    }  }}复制代码

Redux没有单独保存middleware的地方,但是通过展开符的...middlewares,我们也可以知道至少一开始的middlewares是一个数组的形式。

执行

执行的代码,还是上面那段代码片段。

我们可以看到applyMiddleware()中,对传入的middlewares做了简单的封装,目的是为了让每个middleware在执行的时候可以拿到当前的一些环境和一些必要的接口函数。也就是上面那个高阶函数logger所需要的三个参数store,next,action

一开始是middlewares.map(middleware => middleware(middlewareAPI)),而middlewareAPI传入了getStatedispatch接口(dispatch接口暂时没有用)。这一步就实现了上面高阶函数logger所需要的参数store

然后是我们看到好多次的compose函数,我们找到compose函数的实现。

export default function compose(...funcs) {  if (funcs.length === 0) {    return arg => arg  }  if (funcs.length === 1) {    return funcs[0]  }  return funcs.reduce((a, b) => (...args) => a(b(...args)))}复制代码

我们看到compose对传入的中间件函数,通过Array.reduce函数处理了一下。最终的函数应该大概类似下面这个格式

// 加入函数名next方便后面理解function chain(...args) {  return () => {    return a(function next(...args) {      return b(function next(...args) {        return c(...args);      })    })  }}复制代码

这里已经再次出现了我们熟悉的洋葱模型。同时将下一个组件已参数(next)的形式传入当前的中间件,这里就完成了上面的高阶函数logger所需要的第二个参数next,在中间件内部调用next函数就可以继续中间节的流程。

最后传入了store.dispatch也就是高阶函数logger所需要的第二个参数action,这个就不用多数了,就是将我们刚刚得到的洋葱格式的函数调用一下,通过闭包使得每个中间节都可以拿到store.dispatch

总结

至此,ReduxKoa的中间件的介绍就差不多了,两者都是以数组的形式保存了中间件,执行的时候都是创建了一个类似洋葱模型的函数结构,也都是将一个包裹下一个中间件的函数当做next,传入当前中间件,使得当前中间件可以通过调用next来执行洋葱模型,同时在next执行的前后都可以写逻辑代码。不同的是Koa1是通过遍历生成的,Koa2是通过递归来生成的,redux是通过reduce来生成的(和Koa1的遍历类似)。

所以中间件其实都基本类似,所以好好的理解了一种中间件的实现方式,其他的学起来就很快了(只是表示前端这一块哦)。

转载于:https://juejin.im/post/5aeefcf1f265da0b8d41d404

你可能感兴趣的文章
查看Windows上开启的服务
查看>>
linux 常用命令
查看>>
Java 加解密技术系列之 DH
查看>>
VirtualBox三种联网方式
查看>>
Python中使用pickle持久化(保存)对象
查看>>
python3 pelican 搭建静态博客
查看>>
Android 自定义组合控件小结
查看>>
Android学习进阶路线导航线路(Android源码分享)
查看>>
解决Maven和Mybatis整合时打包漏掉mapper的xml文件及其它资源
查看>>
PHP面向对象访问控制public,protected,private
查看>>
MyBatis学习笔记二:增删改查
查看>>
SaltStack安装Tomcat
查看>>
java随机数
查看>>
数据库学习之--Oracle 架构与MySQL架构对比
查看>>
curl 证书访问https站点
查看>>
一篇文章入门Python生态系统
查看>>
Webapp下ClassLoader 加载机制
查看>>
Linux 计算机系统硬件核心知识总结
查看>>
php高级研发或架构师必了解---很多问题面试中常问到!
查看>>
使用DOM解析XML文件——构建实时地震信息列表
查看>>