koa中间执行机制
start
基于 koa 2.11 按以下流程分析:
const Koa = require('koa');
const app = new Koa();
const one = (ctx, next) => {
console.log('1-Start');
next();
ctx.body = { text: 'one' };
console.log('1-End');
}
const two = (ctx, next) => {
console.log('2-Start');
next();
ctx.body = { text: 'two' };
console.log('2-End');
}
const three = (ctx, next) => {
console.log('3-Start');
ctx.body = { text: 'three' };
next();
console.log('3-End');
}
app.use(one);
app.use(two);
app.use(three);
app.listen(3000);
app.use()
use 方法定义在 koa/lib/application.js 中:
use(fn) {
// check middleware type, must be a function
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
// 兼容 generator
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;
}
this.middleware
这就是一个数组,用来存放所有中间件,然后按顺序执行。
this.middleware = [];
app.listen()
这个方法定义在 koa/lib/application.js 中:
listen(...args) {
debug('listen');
// 创建 http 服务并监听
const server = http.createServer(this.callback());
return server.listen(...args);
}
this.callback()
callback() {
// 处理中间件
const fn = compose(this.middleware);
if (!this.listenerCount('error')) this.on('error', this.onerror);
const handleRequest = (req, res) => {
// 创建 Context
const ctx = this.createContext(req, res);
// 执行中间件处理请求和响应
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
this.handleRequest
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = err => ctx.onerror(err);
// 将响应发出的函数
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
// 这里会将 ctx 传给中间件进行处理,
// 当中间件流程走完后,
// 会执行 then 函数将响应发出
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
respond(ctx)
function respond(ctx) {
// 省略其他代码
// ...
// 发出响应
res.end(body);
}
捋一捋流程,由上面的代码可以知道,存放中间的数组是通过 compose 方法进行处理,然后返回一个fnMiddleware函数,接着将 Context 传递给这个函数来进行处理,当fnMiddleware执行完毕后就用respond方法将响应发出。
compose(this.middleware)
compose 函数通过koa-compose引入:
const compose = require('koa-compose');
compose 定义在koajs/compose/index.js下
function compose (middleware) {
// 传入的必须是数组
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
// 数组里面必须是函数
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
return function (context, next) {
// 这个 index 是标识上一次执行的中间件是第几个
let index = -1
// 执行第一个中间件
return dispatch(0)
function dispatch (i) {
// 检查中间件是否已经执行过,
// 举个例子,当执行第一个中间件时 dispatch(0),
// i = 0, index = -1, 说明没有执行过,
// 然后 index = i, 而 index 通过闭包保存,
// 如果执行了多次,就会报错
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
// 通过传入的索引从数组中获取中间件
let fn = middleware[i]
// 如果当前索引等于中间件数组的长度,
// 说明已经中间件执行完毕,
// fn 为 fnMiddleware(ctx) 时没有传入的第二个参数,
// 即 fn = undefined
if (i === middleware.length) fn = next
// fn 为 undefined, 返回一个已经 reolved 的 promise
if (!fn) return Promise.resolve()
try {
// 执行中间件函数并将 dispatch 作为 next 函数传入
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
结束执行流程
现在来捋一下 fnMiddleware的执行流程:
// fnMiddleware 接收两个参数
function (context, next) {
// ....
}
// 将 context 传入,并没有传入 next,
// 所以第一次执行时是没有传入 next 的
fnMiddleware(ctx).then(handleResponse).catch(onerror);
next == undefined 时会结束中间件执行,流程如下:
function dispatch (i) {
//...
// 通过传入的索引从数组中获取中间件,
// 但是因为已经执行完了所有中间件,
// 所以当前 i 已经等于数组长度,
// 即 fn = undefined
let fn = middleware[i]
// 如果当前索引等于中间件数组的长度,
// 说明已经中间件执行完毕,
// 又因为 fnMiddleware(ctx) 时没有传入的第二个参数 next,
// 所以 fn = undefined
if (i === middleware.length) fn = next
// fn 为 undefined, 返回一个已经 reolved 的 promise
// 中间件执行流程结束
if (!fn) return Promise.resolve()
// ...
}
中间件执行流程
上面先说了结束流程,现在说一下如何顺序执行,形成洋葱模型:
function dispatch (i) {
// ...省略其他代码
try {
// 分步骤说明
// 首先通过 bind 将 dispatch 构建为 next 函数
const next = dispatch.bind(null, i + 1);
// 将 ctx, next 传入执行当前中间件,
// 当在中间件中调用 next() 时,
// 本质上是调用 diapatch(i + 1),
// 也就是从数组中获取下一个中间件进行执行,
// 在这时,会中断当前中间件的执行流程转去执行下一个中间件,
// 只有当下一个中间件执行完毕,才会恢复当前中间件的执行
const result = fn(context, next);
// 中间件执行完毕,返回已经 resolve 的 promise,
// 那么上一个中间件接着执行剩下的流程,
// 这样就形成了洋葱模型
return Promise.resolve(result);
} catch (err) {
return Promise.reject(err)
}
}
开头的例子执行结果如下:
const one = (ctx, next) => {
console.log('1-Start');
next();
ctx.body = { text: 'one' };
console.log('1-End');
}
const two = (ctx, next) => {
console.log('2-Start');
next();
ctx.body = { text: 'two' };
console.log('2-End');
}
const three = (ctx, next) => {
console.log('3-Start');
ctx.body = { text: 'three' };
next();
console.log('3-End');
}
// 1-Start
// 2-Start
// 3-Start
// 3-End
// 2-End
// 1-End
// 而 ctx.body 最终为 { text: 'one' }
next()
没有调用 next()
// 没有调用 next() 函数
app.use((ctx, next) => {
console.log('Start');
ctx.body = { text: 'test' };
console.log('End');
});
因为 next 函数本质上就是通过dispatch(i + 1)来调用下一个中间件,如果没有调用 next 函数,就无法执行下一个中间件,那么就代表当前中间件流程执行结束。
多次调用 next()
app.use((ctx, next) => {
console.log('Start');
ctx.body = { text: 'test' };
// 多次调用 next 函数
next(); // 本质上是 dispatch(i + 1)
next(); // 本质上是 dispatch(i + 1)
console.log('End');
});
这里假设 next 为 dispatch(3),那么 index 就为 2,第一次执行 next 函数时,会发生如下逻辑:
// index == 2
// i == 3
// 不会报错
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
// 赋值后 index 为 3 了
index = i
假设第三个中间件是最后一个中间件,那么执行完第一次 next 函数会立即执行第二个 next 函数,依然执行这个逻辑,但是 index 已经为 3 了,所以会导致报错:
// index == 3
// i == 3
// 报错
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
koa中间执行机制的更多相关文章
- JavaScript定时器与执行机制解析
从JS执行机制说起 浏览器(或者说JS引擎)执行JS的机制是基于事件循环. 由于JS是单线程,所以同一时间只能执行一个任务,其他任务就得排队,后续任务必须等到前一个任务结束才能开始执行. 为了避免因为 ...
- 深入理解JVM--类的执行机制
在完成将class文件信息加载到JVM并产生class对象之后,就可以执行Class对象的静态方法或者实例方法对对象进行调用了.JVM在源代码编译阶段将源代码编译为字节码文件,字节码是一种中间代码的方 ...
- linux上应用程序的执行机制
linux上应用程序的执行机制 执行文件是如何在shell中被"执行"的.本文中尽可能少用一些源码,免得太过于无 聊,主要讲清这个过程,感兴趣的同学可以去查看相应的源码了解更多的信 ...
- java执行机制
java代码编译是由Java源码编译器来完成,流程图如下所示: Java字节码的执行是由JVM执行引擎来完成,流程图如下所示: Java代码编译和执行的整个过程包含了以下三个重要的机制: Java源码 ...
- 一段代码说明javascript闭包执行机制
假设你能理解以下代码的执行结果,应该就算理解闭包的执行机制了. var name = "tom"; var myobj = { name: "jackson", ...
- Java虚拟机JVM内存分区及代码执行机制
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt230 1. JVM体系结构 图1 JVM体系结构 方法区:存放JVM ...
- sql执行机制
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp38 sql执行机制 1.对于普通的sql语句只有where条件的执行机制 ...
- javascript执行机制
文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我. 不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的 ...
- 彻底弄懂 JavaScript 执行机制
本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我. 不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定 ...
随机推荐
- org.apache.subversion.javahl.ClientException: Item is not readable 解决办法
在使用eclise安装的插件subclipse查看svn的提交历史记录的时候,提示org.apache.subversion.javahl.ClientException: Item is not r ...
- DataX-MysqlWriter 插件文档
:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdow ...
- 1068 万绿丛中一点红 (20分)C语言
对于计算机而言,颜色不过是像素点对应的一个 24 位的数值.现给定一幅分辨率为 M×N 的画,要求你找出万绿丛中的一点红,即有独一无二颜色的那个像素点,并且该点的颜色与其周围 8 个相邻像素的颜色差充 ...
- 负载均衡基本原理与lvs
前言: 之前在山西的项目上使用的是lvs下的NAT模式,但另外两个模式并没有涉及,今天系统的整理下关于负载均衡的相关理论与lvs各模式的相关优点与不足,知其然与所以然,而后能针对性的应用: 基本介绍 ...
- SpringBootTest 测试工具
以下内容,翻译自官方文档,并结合了学习过程的demo. Spring Boot提供了许多实用程序和注解,帮助测试应用程序.测试支持由两个模块提供:spring-boot-test 包含核心项,spri ...
- 27.openpyxl 向指定单元格添加图片并修改图片大小 以及修改单元格行高列宽
openpyxl 向指定单元格添加图片并修改图片大小 以及修改单元格行高列宽 from openpyxl import Workbook,load_workbook from openpyxl.dra ...
- AES中ECB模式的加密与解密(Python3.7)
本文主要解决的问题 本文主要是讲解AES加密算法中的ECB模式的加密解密的Python3.7实现.具体AES加密算法的原理这里不做过多介绍,想了解的可以参考文末的参考链接. 主要解决了两个问题: 在P ...
- hadoop搭建
一.前期准备 1.1 静态ip,请查看虚拟机安装不放呢 1.2 hostname 以及 hosts文件修改 cat /etc/hostname 不同的机器设置不同的名字 cat /etc/hosts ...
- vue vuex开发中遇到的问题及解决小技巧
1.在vue的开发中,如果使用了vuex,数据的组装,修改时在mutations中,页面是建议修改变量值的,如果强制修改,控制台就会出现错误.如下: 这种错误虽然不会影响结果,但是是vuex不提倡的方 ...
- .NET为什么要使用异步(async)编程?⭐⭐⭐⭐⭐
.NET为什么要使用异步(async)编程? 有几点坐下笔记 ⭐⭐⭐⭐: 1. 同步方法 static void Main(string[] args) { Console.WriteLine($&q ...