深入理解 Koa 框架中间件原理


Posted in Javascript onOctober 18, 2018

Node 主要用在开发 Web 应用,koa 是目前 node 里最流行的 web 框架。

在 Node 开启一个 http 服务简直易如反掌,官网 demo。

const http = require("http");

const server = http.createServer((req, res) => {
 res.statusCode = 200;
 res.setHeader("Content-Type", "text/plain");
 res.end("Hello World\n");
});

const hostname = "127.0.0.1";
const port = 3000;
server.listen(port, hostname, () => {
 console.log(`Server running at http://${hostname}:${port}/`);
});
  • 引入 http 模块, http 的 createServer 方法创建了一个 http.Server 的实例。
  • server 监听 3000 端口。
  • 我们传入到 createServer 里的函数实际是监听 request 事件的回调,每当请求进来,监听函数就会执行。
  • request 事件的监听函数,其函数接受两个参数,分别是 req 和 res 。其中 req 是一个可读流, res 是一个可写流。我们通过 req 获取 http 请求的所有信息,同时将数据写入到 res 来对该请求作出响应。

koa 应用

koa 如何创建一个 server, 直接上个官网的例子

const Koa = require("koa");
const app = new Koa();

// x-response-time

app.use(async (ctx, next) => {
 const start = Date.now();
 await next();
 const ms = Date.now() - start;
 ctx.set("X-Response-Time", `${ms}ms`);
});

// logger

app.use(async (ctx, next) => {
 const start = Date.now();
 await next();
 const ms = Date.now() - start;
 console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});

// response

app.use(async ctx => {
 ctx.body = "Hello World";
});

app.listen(3000);

中间件概念在编程中使用广泛, 不管是前端还是后端, 在实际编程中或者框架设计都有使用到这种实用的模型。

基本上,Koa 所有的功能都是通过中间件实现的。

每个中间件默认接受两个参数,第一个参数是 Context 对象,第二个参数是 next 函数。只要调用 next 函数,就可以把执行权转交给下一个中间件。

如果中间件内部没有调用 next 函数,那么执行权就不会传递下去。

多个中间件会形成一个栈结构(middle stack),以“先进后出”(first-in-last-out)的顺序执行。整个过程就像,先是入栈,然后出栈的操作。

上面代码的执行顺序是:

请求 ==> x-response-time 中间件 ==> logger 中间件 ==> response中间件 ==> logger 中间件 ==> response-time 中间件 ==> 响应

理解 Koa 的中间件机制(源码分析)

阅读源码,化繁为简,我们看看 koa 的中间件系统是如何实现的。

class Application extends Emitter {
 constructor() {
  super();
  this.middleware = [];
 },

 use(fn) {
  this.middleware.push(fn);
  return this;
 },

 callback() {
  const fn = compose(this.middleware);

  return function(req, res) {
   return fn(ctx);
  };
 },

 listen(...args) {
  const server = http.createServer(this.callback());
  return server.listen(...args);
 }
}

好了,精简结束,一不小心,去枝末节,最后只剩下不到 20 行代码。

这就是框架的核心,简化后的代码非常清晰,有点不可思议,但核心就是这么简单。

我们先分析以上代码做了什么事。

  • 我们定义了一个 middleware 数组来存储中间件。
  • 我们定一个了一个 use 方法来注册一个中间件。其实就是简单的 push 到自身的 mideware 这个数组中。
  • 我们还使用了一个 compose 方法,传入 middleware ,应该是做了一些处理,返回了一个可执行的方法。

你一定对中间的 compose 方法很好奇,初此之外的代码都容易理解,唯独这个 compose 不太知道究竟做了什么。

其实, compose 就是整个中间件框架的核心。

compose 之外,代码已经很清楚的定义了

  • 中间件的存储
  • 中间件的注册

而 compose 方法做了最为重要的一件事

  • 中间件的执行

核心源码 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();
   try {
    return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
   } catch (err) {
    return Promise.reject(err);
   }
  }
 };
}

我试图去简化一下这个方法,但方法本身已经足够简洁。

代码很简洁。

通过 next()传递 实现中间件调用, 结合 Promise 采用 递归调用 的通知机制。

看图

深入理解 Koa 框架中间件原理

这种形式的控制流让整个 Koa 框架中间件的访问呈现出 自上而下的中间件流 + 自下而上的 response 数据流 的形式。

Koa 本身做的工作仅仅是定制了中间件的编写规范,而不内置任何中间件。一个 web request 会通过 Koa 的中间件栈,来动态完成 response 的处理。

koa 在中间件语法上面采用了 async + await 语法来生成 Promise 形式的程序控制流。

总结

koa 是非常精简的框架, 其中的精粹思想就是洋葱模型(中间件模型), koa 框架的中间件模型非常好用并且简洁, 但是也有自身的缺陷, 一旦中间件数组过于庞大, 性能会有所下降,我们需要结合自身的情况与业务场景作出最合适的选择.

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

Javascript 相关文章推荐
利用javascript中的call实现继承
Jan 22 Javascript
jQuery插件原来如此简单 jQuery插件的机制及实战
Feb 07 Javascript
js点击更换背景颜色或图片的实例代码
Jun 25 Javascript
Jquery如何实现点击时高亮显示代码
Jan 22 Javascript
对比分析AngularJS中的$http.post与jQuery.post的区别
Feb 27 Javascript
javascript中的正则表达式使用详解
Aug 30 Javascript
jQuery入门之层次选择器实例简析
Dec 11 Javascript
EasyUi 打开对话框后控件赋值及赋值后不显示的问题解决办法
Jan 19 Javascript
一篇文章搞定JavaScript类型转换(面试常见)
Jan 21 Javascript
vue-music关于Player播放器组件详解
Nov 28 Javascript
微信小程序配置服务器提示验证token失败的解决方法
Apr 03 Javascript
vue项目中在外部js文件中直接调用vue实例的方法比如说this
Apr 28 Javascript
JS实现十分钟倒计时代码实例
Oct 18 #Javascript
基于Vue实现可以拖拽的树形表格实例详解
Oct 18 #Javascript
JavaScript的词法结构精华篇
Oct 17 #Javascript
Javascript中parseInt的正确使用方式
Oct 17 #Javascript
教你如何编写Vue.js的单元测试的方法
Oct 17 #Javascript
详解vue如何使用rules对表单字段进行校验
Oct 17 #Javascript
Vue绑定内联样式问题
Oct 17 #Javascript
You might like
解析php中call_user_func_array的作用
2013/06/07 PHP
php+ajax制作无刷新留言板
2015/10/27 PHP
学习php设计模式 php实现享元模式(flyweight)
2015/12/07 PHP
从性能方面考虑PHP下载远程文件的3种方法
2015/12/29 PHP
PHP实现字符串翻转功能的方法【递归与循环算法】
2017/11/03 PHP
基于jquery的多彩百分比 动态进度条 投票效果显示效果实现代码
2011/08/28 Javascript
JavaScript 模式之工厂模式(Factory)应用介绍
2012/11/15 Javascript
仿百度联盟对联广告实现代码
2014/08/30 Javascript
快速掌握Node.js模块封装及使用
2016/03/21 Javascript
获取input标签的所有属性的方法
2016/06/28 Javascript
快速实现jQuery多级菜单效果
2017/02/01 Javascript
详解用vue.js和laravel实现微信授权登陆
2017/06/23 Javascript
JS失效 提示HTML1114: (UNICODE 字节顺序标记)的代码页 utf-8 覆盖(META 标记)的冲突的代码页 utf-8
2017/06/23 Javascript
Vuejs实现购物车功能
2017/11/05 Javascript
微信小程序使用wx.request请求服务器json数据并渲染到页面操作示例
2019/03/30 Javascript
python Django模板的使用方法(图文)
2013/11/04 Python
35个Python编程小技巧
2014/04/01 Python
Python 爬虫学习笔记之单线程爬虫
2016/09/21 Python
python中lambda()的用法
2017/11/16 Python
10个Python小技巧你值得拥有
2018/09/29 Python
Python用5行代码写一个自定义简单二维码
2018/10/21 Python
python 递归深度优先搜索与广度优先搜索算法模拟实现
2018/10/22 Python
浅谈django rest jwt vue 跨域问题
2018/10/26 Python
浅谈Pandas Series 和 Numpy array中的相同点
2019/06/28 Python
python opencv 二值化 计算白色像素点的实例
2019/07/03 Python
Python 将 QQ 好友头像生成祝福语的实现代码
2020/05/03 Python
潘多拉珠宝英国官方网上商店:PANDORA英国
2018/06/12 全球购物
耐克亚太地区:Nike APAC
2019/12/07 全球购物
解释一下ArrayList Vector和LinkedList的实现和区别
2013/04/26 面试题
卫校中专生个人自我评价
2013/09/19 职场文书
党员学习十八大感想
2014/01/17 职场文书
我的理想演讲稿
2014/04/30 职场文书
汽车检测与维修专业求职信
2014/07/04 职场文书
2014年维修工作总结
2014/11/22 职场文书
使用Redis做预定库存缓存功能
2022/04/02 Redis
常用的文件对应的MIME类型汇总
2022/04/26 HTML / CSS