koa2 use里面的next到底是什么
koa2短小精悍,女人不爱男人爱。
之前一只有用koa写一点小程序,自认为还吼吼哈,知道有一天某人问我,你说一下 koa或者express中间件的实现原理。然后我就支支吾吾,好久吃饭都不香。
那么了解next的最好办法是什么, 百度,谷歌,知乎? 没错,肯定有用,我觉得最有用的是看源码和debug去理解。
先看下面的一段代码 ,会输出什么,只会输出 X-Response-Time
- const Koa = require('koa');
- const app = new Koa();
- // x-response-time
- app.use(async (ctx) => {
- const start = Date.now();
- //await next();
- const ms = Date.now() - start;
- ctx.set('X-Response-Time', `${ms}ms`);
- console.log('X-Response-Time', `${ms}ms`)
- });
- // logger
- app.use(async (ctx) => {
- const start = Date.now();
- //await next();
- const ms = Date.now() - start;
- console.log(`${ctx.method} ${ctx.url} - ${ms}`);
- });
- // response
- app.use(async ctx => {
- console.log('Hello World')
- ctx.body = 'Hello World';
- });
- app.listen(3000);
然后修改成如下代码,会依次输出
- 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`);
- console.log('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 => {
- console.log('Hello World')
- ctx.body = 'Hello World';
- });
- app.listen(3000);
从上面的结果看来,发现什么没有,没有next 就没有下面的执行,可就简单的一个 await next(), 为嘛会有这种效果,这里,我首先简单说一下koa2中间件的实现原理。
这里先从 koa的使用说起
- const Koa = require('koa');
- const app = new Koa();
- app.use(async (ctx, next) => {
- const start = Date.now();
- await next();
- const ms = Date.now() - start;
- console.log(`${ctx.method} ${ctx.url} - ${ms}`);
- });
- app.listen(3000);
我们顺藤摸瓜,打开 koa里面的application.js (或者直接debug进入),
1.首先看 use ,就是push一个函数到 this.middleware
2. 再看listen, 方法里面 http.createServer(this.callBack), this.callBack返回的是 function(req,res){......}的函数,连起来就是 http.createServer(function(req,res){....}),标准的http创建服务的方法
3. 最后看callback,里面的核心方法, compose(this.middleware) 返回一个promise,处理完毕后再执行 handleResponse
这三个连起来,就是每次请求的时候,先进入callback, compose中间件,执行完毕后,接着处理请求。那剩下的重点变为 compose
- 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;
- }
- listen(...args) {
- debug('listen');
- const server = http.createServer(this.callback());
- return server.listen(...args);
- }
- callback() {
- const fn = compose(this.middleware);
- if (!this.listeners('error').length) this.on('error', this.onerror);
- const handleRequest = (req, res) => {
- res.statusCode = 404;
- const ctx = this.createContext(req, res);
- const onerror = err => ctx.onerror(err);
- const handleResponse = () => respond(ctx);
- onFinished(res, onerror);
- return fn(ctx).then(handleResponse).catch(onerror);
- };
- return handleRequest;
- }
我们继续深入研究 compose,看源码,核心依旧是标粗的部分,核心的核心就是dispatch, dispatch会根据 middleware 的长度,依次执行。
- 'use strict'
- /**
- * Expose compositor.
- */
- module.exports = compose
- /**
- * Compose `middleware` returning
- * a fully valid middleware comprised
- * of all those which are passed.
- *
- * @param {Array} middleware
- * @return {Function}
- * @api public
- */
- 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!')
- }
- /**
- * @param {Object} context
- * @return {Promise}
- * @api public
- */
- 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)
- }
- }
- }
- }
注意下面,如果 next为空,直接返回,也就出现了我们第一段代码的情况,后面的中间件就game over了。
- if (i === middleware.length) fn = next
- if (!fn) return Promise.resolve()
在往下分析,假定现在执行第一个fn,这个时候第一个fn是什么
- return Promise.resolve(fn(context, function next () {
- return dispatch(i + 1)
- }))
这时候fn为如下,
- fn = async (ctx, next) => {
- const start = Date.now();
- await next();
- const ms = Date.now() - start;
- ctx.set('X-Response-Time', `${ms}ms`);
- console.log('X-Response-Time', `${ms}ms`)
- }
与上面的参数对应关系如下
context :ctx,
next : function next(){ return dispatch(i+1)}
所以 await next() 就等于 await function next(){ return dispatch(i+1)} , 而 dispatch(i+1)就进入了下一个中间件了。
核心就是 dispatch(i+1),也就是dispatch(1) , dispatch本身返回promise, 所以你就在这里 await 。
依此类推 disptach(1) 会执行 this.middleware[1], 那个时候 fn就为 logger执行的函数,就这么推下去。
关于结束,还是 next 不存在的时候。 结果完毕后,再依次往上走。
所以执行的顺序是越先注册越后执行, 当然还得看你 await next() 放在什么位置。 因为这里我的 console.log都放在了 await的后面,都放到前面,结果如何,亲自测试一下喽。
最后简单的模拟一下 Promise.resolve(fn()),
1. fn为一个异步函数,所以里面可以await
2. fn最后返回的是一个Promise对象
3. 当Promise.then, Promise.resolve返回是一个Promise对象时,会执行该Promise对象,并进入下一个环节
4 . 所以p1, p2依次执行,最后结果为6
- var p1 = function () {
- return new Promise((resolve, reject) => {
- setTimeout(function () {
- console.log('p1', new Date().toLocaleString())
- resolve(1)
- }, 2000)
- })
- }
- var p2 = function () {
- return new Promise((resolve, reject) => {
- setTimeout(function () {
- console.log('p2', new Date().toLocaleString())
- resolve(6)
- }, 4000)
- })
- }
- console.log('start', new Date().toLocaleString())
- Promise.resolve(fn()).then(r => {
- console.log('end', new Date().toLocaleString())
- console.log(r)
- })
- async function fn() {
- let a = await p1()
- let b = 4
- return p2()
- }
- // start 2018/3/15 下午8:16:37
- // p1 2018/3/15 下午8:16:39
- // p2 2018/3/15 下午8:16:43
- // end 2018/3/15 下午8:16:43
- // 6
koa2 use里面的next到底是什么的更多相关文章
- koa2 中间件里面的next到底是什么
koa2短小精悍,女人不爱男人爱. 之前一只有用koa写一点小程序,自认为还吼吼哈,知道有一天某人问我,你说一下 koa或者express中间件的实现原理.然后我就支支吾吾,好久吃饭都不香. 那么了解 ...
- JavaScript里面的arguments到底是个啥?
类数组对象:arguments 总所周知,js是一门相当灵活的语言.当我们在js中在调用一个函数的时候,我们经常会给这个函数传递一些参数,js把传入到这个函数的全部参数存储在一个叫做arguments ...
- 学习hash_map从而了解如何写stl里面的hash函数和equal或者compare函数
---恢复内容开始--- 看到同事用unordered_map了所以找个帖子学习学习 http://blog.sina.com.cn/s/blog_4c98b9600100audq.html (一)为 ...
- VC代码生成里面的/MT /MTd /MD /MDd的意思
VC代码生成里面的/MT /MTd /MD /MDd的意思. 意思上已经很明白了.但是往往很多人弄不清楚到底怎么选择. /MT是 "multithread, static version ” ...
- Activity往另外一个Activity传值,Fragment获取另外一个Activity里面的值。
在oneActivity中实现跳转到MainActivity //intent 用来跳转另外一个MainActivity,bundle传值到MainActivity Intent Ma ...
- Java基本概念(2)J2EE里面的2是什么意思
J2EE里面的2是什么意思 J2SE,J2SE,J2ME中2的含义要追溯要1998年.1998年Java 1.2版本发布,1999年发布Java 1.2的标准版,企业版,微型版三个版本,为了区分这三个 ...
- 在wex5平台grid里面的gridselect下拉不能显示汉字问题
当grid里面有gridSelect组件的时候,gridSelect里面的bind-ref是对应的数据库存入字段(int类型),bind-labelRef是对应的计算字段(视图里面的),而option ...
- dede文章调用时过滤调 body里面的style属性和值
dede 发布文章的时候会在里面的标签中添加一些style 属性,现在改网站想去掉这些属性和里面的值,因为文章太多所以就用下面的方法 \include\arc.listview.class.php 在 ...
- 提取数据库字段里面的值,并改变+图片懒加载,jquery延迟加载
要求:手机端打开某个页面的详细信息,因为网速或者别的原因,响应太慢,因为图片大的原因,希望先进来,图片在网页运行的情况再慢慢加载(jquer延迟加载) http://www.w3cways.com/1 ...
随机推荐
- Codility---MaxProfit
Task description A zero-indexed array A consisting of N integers is given. It contains daily prices ...
- javacpp-opencv图像处理之2:实时视频添加图片水印,实现不同大小图片叠加,图像透明度控制,文字和图片双水印
欢迎大家积极开心的加入讨论群 群号:371249677 (点击这里进群) javaCV图像处理系列: javaCV图像处理之1:实时视频添加文字水印并截取视频图像保存成图片,实现文字水印的字体.位置. ...
- winform中的 datagriview 字段自动填充长度
在winfrom 的 datagridview 中 绑定字段 经常回在最后面空出一部分来,显得不美观, 现在教大家如何让它自适应宽度 public static void Autogrid(DataG ...
- JS数组+JS循环题
先看JS循环作业题: 一.一张纸的厚度是0.0001米,将纸对折,对折多少次厚度超过珠峰高度8848米 <script type="text/javascript"> ...
- 进程控制fork与vfork
1. 进程标识符 在前面进程描述一章节里已经介绍过进程的两个基本标识符pid和ppid,现在将详细介绍进程的其他标识符. 每个进程都有非负的整形表示唯一的进程ID.一个进程终止后,其进程ID就可以再次 ...
- 解决Ubuntu开关机动画不正常方法
联想的笔记本,显卡NVIDIA GT218M,默认使用开源的驱动,但挂起后,再唤醒就黑屏回不到桌面. 1.解决办法:安装NVIDIA专有驱动 $sudo apt-get install nvidia- ...
- Mysql数据库存储emoji表情
emoji表情需要使用编码格式未utf8mb4,mysql数据库版本要5.5以上,我用的是5.6,因为只有5.5以上支持utf8mb4. 1.数据库编码设定为utf8mb4,如果建库时指定的是utf8 ...
- war包中少了class文件
用eclipse的maven工具打包(执行mvn clean install 和 mvn package)后,再启动wildfly, war包应该会自动复制到wildfly的deployment ...
- jquery fadeIn用法
$("#msgSpan").fadeIn("slow"); setTimeout('$("#msgSpan").hide("slo ...
- 优化mysql数据库的几个步骤
析问题: 1. 开启慢查询日志. 这个步骤就是为了记录慢查询的sql,为下个步骤做准备,此步骤相关的知识点有如下: 1. show variables like '%slow_query_log%'; ...