node中koa中间件机制详解


Posted in Javascript onAugust 22, 2017

koa

koa是由express原班人马打造的一个更小、更富有表现力、更健壮的web框架。

在我眼中,koa的确是比express轻量的多,koa给我的感觉更像是一个中间件框架,koa只是一个基础的架子,需要用到的相应的功能时,用相应的中间件来实现就好,诸如路由系统等。一个更好的点在于,express是基于回调来处理,至于回调到底有多么的不好,大家可以自行搜索来看。koa1基于的co库,所以koa1利用Generator来代替回调,而koa2由于node对async/await的支持,所以koa2利用的是async/await。关于async以及co库等,大家可以参考我之前写过的一篇文章(理解async)。koa可以说是一个各种中间件的架子,下面就来看一下koa对于中间件部分的实现:

koa1的中间件

koa1主要利用的是Generator来实现,一般来说,koa1的一个中间件大概是长这个样子的:

app.use(function *(next){
  console.log(1);
  yield next;
  console.log(5);
});
app.use(function *(next){
  console.log(2);
  yield next;
  console.log(4);
});
app.use(function *(){
  console.log(3);
});

这样的输出会是1, 2, 3, 4, 5,koa的中间件的实现主要依靠的是koa-compose:

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(){}

源码非常的简单,实现的功能就是将所有的中间件串联起来,首先给倒数第一个中间件传入一个noop作为其next,再将这个整理后的倒数第一个中间作为next传入倒数第二个中间件,最终得到的next就是整理后的第一个中间件。说起来比较复杂,画图来看:

node中koa中间件机制详解

实现的效果如同上图,与redux需要实现的目标类似,只要遇到了yield next就去执行下一个中间件,利用co库很容易将这个流程串联起来,下面来简单模拟下,中间件完整的实现:

const middlewares = [];

const getTestMiddWare = (loggerA, loggerB) => {
  return function *(next) {
    console.log(loggerA);
    yield next;
    console.log(loggerB);
  }
};
const mid1 = getTestMiddWare(1, 4),
  mid2 = getTestMiddWare(2, 3);

const getData = new Promise((resolve, reject) => {
  setTimeout(() => resolve('数据已经取出'), 1000);
});

function *response(next) {
  // 模拟异步读取数据库数据
  const data = yield getData;
  console.log(data);
}

middlewares.push(mid1, mid2, response);
// 简单模拟co库
function co(gen) {
  const ctx = this,
    args = Array.prototype.slice.call(arguments, 1);
  return new Promise((reslove, reject) => {
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    const baseHandle = handle => res => {
      let ret;
      try {
        ret = gen[handle](res);
      } catch(e) {
        reject(e);
      }
      next(ret);
    };
    const onFulfilled = baseHandle('next'),
      onRejected = baseHandle('throw');
      
    onFulfilled();
    function next(ret) {
      if (ret.done) return reslove(ret.value);
      // 将yield的返回值转换为Proimse
      let value = null;
      if (typeof ret.value.then !== 'function') {
        value = co(ret.value);
      } else {
        value = ret.value;
      }
      if (value) return value.then(onFulfilled, onRejected);
      return onRejected(new TypeError('yield type error'));
    }
  });
}
// 调用方式
const gen = compose(middlewares);
co(gen);

koa2的中间件

随着node对于async/await的支持,貌似不需要再借助于co这种工具库了,直接利用原生的就好,于是koa也做出了改变,来看目前的koa-compose:

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()
   // 用Promise包裹中间件,方便await调用
   try {
    return Promise.resolve(fn(context, function next () {
     return dispatch(i + 1)
    }))
   } catch (err) {
    return Promise.reject(err)
   }
  }
 }
}

koa-compose利用了Promise,koa2的中间件的参数也有一个变为了两个,而且执行下一个的中间件利用的是await next(),要达到与上面的示例代码的相同效果,需要更改中间件的写法:

const middlewares = [];
const getTestMiddWare = (loggerA, loggerB) => async (ctx, next) => {
  console.log(loggerA);
  await next();
  console.log(loggerB);
};

const mid1 = getTestMiddWare(1, 4),
  mid2 = getTestMiddWare(2, 3);
const response = async () => {
  // 模拟异步读取数据库数据
  const data = await getData();
  console.log(data);
};
const getData = () => new Promise((resolve, reject) => {
  setTimeout(() => resolve('数据已经取出'), 1000);
});
middlewares.push(mid1, mid2);

// 调用方式
compose(middlewares)(null, response);

如何做到兼容

可以看到的是,koa1与koa2对于中间件的实现还是有着很多的不同的,将koa1的中间件直接拿到koa2下面来使用肯定是会出现错误的,如何兼容这两个版本也成了一个问题,koa团队写了一个包来是koa1的中间件可以用于koa2中,叫做koa-convert,先来看看这个包怎么使用:

function *mid3(next) {
  console.log(2, 'koa1的中间件');
  yield next;
  console.log(3, 'koa1的中间件');
}
convert.compose(mid3)

来看下这个包实现的思路:

// 将参数转为数组,对每一个koa1的中间件执行convert操作
convert.compose = function (arr) {
 if (!Array.isArray(arr)) {
  arr = Array.from(arguments)
 }
 return compose(arr.map(convert))
}
// 关键在于convert的实现
const convert = mw => (ctx, next) => {
  // 借助co库,返回一个Promise,同时执行yield
  return co.call(ctx, mw.call(ctx, createGenerator(next)));
};

function * createGenerator (next) {
 /*
   next为koa-compomse中:
   function next () {
     return dispatch(i + 1)
   }
 */
 return yield next()
 // 执行完koa1的中间件,又回到了利用await执行koa2中间件的正轨
}

个人感觉koa-convert的思路就是对Generator封装一层Promise,使上一个中间件可以利用await next()的方式调用,对于Generator的执行,利用co库,从而达到了兼容的目的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
首页图片漂浮效果示例代码
Jun 05 Javascript
javascript定义变量时有var和没有var的区别探讨
Jul 21 Javascript
js获取input长度并根据页面宽度设置其大小及居中对齐
Aug 22 Javascript
jQuery中after()方法用法实例
Dec 25 Javascript
深入浅出理解javaScript原型链
May 09 Javascript
Jquery ajax 同步阻塞引起的UI线程阻塞问题
Nov 17 Javascript
简述jQuery ajax的执行顺序
Jan 05 Javascript
JavaScript图像延迟加载库Echo.js
Apr 05 Javascript
react高阶组件经典应用之权限控制详解
Sep 07 Javascript
在微信小程序中渲染HTML内容的方法示例
Sep 28 Javascript
对vuex中store和$store的区别说明
Jul 24 Javascript
js实现飞机大战小游戏
Aug 26 Javascript
理解javascript async的用法
Aug 22 #Javascript
React Native之TextInput组件解析示例
Aug 22 #Javascript
EasyUI的DataGrid每行数据添加操作按钮的实现代码
Aug 22 #Javascript
浅谈箭头函数写法在ReactJs中的使用
Aug 22 #Javascript
Extjs 中的 Treepanel 实现菜单级联选中效果及实例代码
Aug 22 #Javascript
bootstrap3-dialog-master模态框使用详解
Aug 22 #Javascript
关于JS与jQuery中的文档加载问题
Aug 22 #jQuery
You might like
PHP制作图型计数器的例子
2006/10/09 PHP
经典PHP加密解密函数Authcode()修复版代码
2015/04/05 PHP
Yii框架引用插件和ckeditor中body与P标签去除的方法
2017/01/19 PHP
PHP二维数组实现去除重复项的方法【保留各个键值】
2017/12/21 PHP
JavaScript高级程序设计(第3版)学习笔记5 js语句
2012/10/11 Javascript
js arguments对象应用介绍
2012/11/28 Javascript
javascript数组快速打乱重排的方法
2014/01/02 Javascript
JS使用parseInt解析数字实现求和的方法
2015/08/05 Javascript
Jquery easyui 实现动态树
2015/11/17 Javascript
三个js循环的关键字示例(for与while)
2016/02/16 Javascript
基于Bootstrap实现下拉菜单项和表单导航条(两个菜单项,一个下拉菜单和登录表单导航条)
2016/07/22 Javascript
学习使用Bootstrap页面排版样式
2017/05/11 Javascript
Angular 2父子组件数据传递之@ViewChild获取子组件详解
2017/07/04 Javascript
JS中type=&quot;button&quot;和type=&quot;submit&quot;的区别
2017/07/04 Javascript
Rollup处理并打包JS文件项目实例代码
2018/05/31 Javascript
在vue中对数组值变化的监听与重新响应渲染操作
2020/07/17 Javascript
javascript实现简易计算器功能
2020/09/23 Javascript
[01:55]TI9显影之尘系列 - Evil Geniuses
2019/08/22 DOTA
解决python写的windows服务不能启动的问题
2014/04/15 Python
Python可跨平台实现获取按键的方法
2015/03/05 Python
python中sys.argv参数用法实例分析
2015/05/20 Python
Python使用matplotlib实现绘制自定义图形功能示例
2018/01/18 Python
浅析Python语言自带的数据结构有哪些
2019/08/27 Python
python selenium实现发送带附件的邮件代码实例
2019/12/10 Python
python基于plotly实现画饼状图代码实例
2019/12/16 Python
Python开发之pip安装及使用方法详解
2020/02/21 Python
python利用opencv实现颜色检测
2021/02/23 Python
CSS3 网页下拉菜单代码解释 中文翻译
2010/02/27 HTML / CSS
雪花秀美国官方网站:韩国著名草本护肤化妆品品牌
2016/10/19 全球购物
全球最大的服务市场:Fiverr
2017/01/03 全球购物
FC-Moto丹麦:欧洲最大的摩托车服装和头盔商店之一
2019/08/20 全球购物
Java语言程序设计测试题改错题部分
2014/07/22 面试题
网络工程师的自我评价
2013/10/02 职场文书
信息技术教学反思
2014/02/12 职场文书
乐观自信演讲稿范文
2014/05/21 职场文书
2016年母亲节寄语
2015/12/04 职场文书