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);

然后修改成如下代码,会依次输出

Hello World
GET / - 8
X-Response-Time 1040ms
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到底是什么的更多相关文章

  1. koa2 中间件里面的next到底是什么

    koa2短小精悍,女人不爱男人爱. 之前一只有用koa写一点小程序,自认为还吼吼哈,知道有一天某人问我,你说一下 koa或者express中间件的实现原理.然后我就支支吾吾,好久吃饭都不香. 那么了解 ...

  2. JavaScript里面的arguments到底是个啥?

    类数组对象:arguments 总所周知,js是一门相当灵活的语言.当我们在js中在调用一个函数的时候,我们经常会给这个函数传递一些参数,js把传入到这个函数的全部参数存储在一个叫做arguments ...

  3. 学习hash_map从而了解如何写stl里面的hash函数和equal或者compare函数

    ---恢复内容开始--- 看到同事用unordered_map了所以找个帖子学习学习 http://blog.sina.com.cn/s/blog_4c98b9600100audq.html (一)为 ...

  4. VC代码生成里面的/MT /MTd /MD /MDd的意思

    VC代码生成里面的/MT /MTd /MD /MDd的意思. 意思上已经很明白了.但是往往很多人弄不清楚到底怎么选择. /MT是 "multithread, static version ” ...

  5. Activity往另外一个Activity传值,Fragment获取另外一个Activity里面的值。

    在oneActivity中实现跳转到MainActivity //intent 用来跳转另外一个MainActivity,bundle传值到MainActivity         Intent Ma ...

  6. Java基本概念(2)J2EE里面的2是什么意思

    J2EE里面的2是什么意思 J2SE,J2SE,J2ME中2的含义要追溯要1998年.1998年Java 1.2版本发布,1999年发布Java 1.2的标准版,企业版,微型版三个版本,为了区分这三个 ...

  7. 在wex5平台grid里面的gridselect下拉不能显示汉字问题

    当grid里面有gridSelect组件的时候,gridSelect里面的bind-ref是对应的数据库存入字段(int类型),bind-labelRef是对应的计算字段(视图里面的),而option ...

  8. dede文章调用时过滤调 body里面的style属性和值

    dede 发布文章的时候会在里面的标签中添加一些style 属性,现在改网站想去掉这些属性和里面的值,因为文章太多所以就用下面的方法 \include\arc.listview.class.php 在 ...

  9. 提取数据库字段里面的值,并改变+图片懒加载,jquery延迟加载

    要求:手机端打开某个页面的详细信息,因为网速或者别的原因,响应太慢,因为图片大的原因,希望先进来,图片在网页运行的情况再慢慢加载(jquer延迟加载) http://www.w3cways.com/1 ...

随机推荐

  1. 了解Java

    一.计算机程序      为了让计算机执行某些操作或解决某个问题而编写一系列有序指令的集合.二.Java  SE(标准版)      1.Java技术的基础和核心:      2.主要由于开发桌面应用 ...

  2. [Lucene]-Lucene基本概述以及简单实例

    一.Lucene基本介绍: 基本信息:Lucene 是 Apache 软件基金会的一个开放源代码的全文检索引擎工具包,是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎.Luc ...

  3. badboy录制兼容性有趣测试

    badboy录制默认是启动IE浏览器,使用badboy录制脚本时,遇到测试系统对IE浏览器不兼容时,就需要考虑换浏览器,修改为其他浏览器(如firefox.chrome)录制,只需要设置该浏览器为默认 ...

  4. js判断是否是ie浏览器且给出ie版本

    之前懒得写判断ie版本js,因为网上关于这方面的代码太多了,所以从网上拷贝了一个,放到项目上才发现由于时效性的问题,代码不生效.就自己写一个吧. 怎么去看浏览器的内核等信息 ---- js的全局对象w ...

  5. 活动页怎么切图photoshop

    一 切固定大小的单个图片 1.用pc打开图像 2.按ctrl+A(全选) 3.点击 选择 ->变换选区 ->拉参考线(把参考线放到最中央)->按回车 ->ctrl+d(取消全选 ...

  6. Jmeter-WINDOWS下的配置部署

    前提:已配置安装JDK环境及已部署TOMCAT. 解压apache-jmeter-2.9.zip文件至目录,我的是D:\Program Files目录. 点击我的电脑----属性----高级----环 ...

  7. AngularJS高级程序设计读书笔记 -- 指令篇 之 内置指令

    1. 内置指令(10-12 章) AngularJS 内置超过 50 个内置指令, 包括 数据绑定,表单验证,模板生成,时间处理 和 HTML 操作. 指令暴露了 AngularJS 的核心功能, 如 ...

  8. python 最佳实践与资源汇总

    python 最佳实践 (部分) 一. 结构化工程 文件 功能 README.rst readme LICENSE 许可证 setup.py 打包和发布管理 requirements.txt 开发依赖 ...

  9. tcp/ip详解 卷1 -- 协议概述

    第一章 概述 分层 TCP/IP 通常被认为是一个四层协议系统. 每一层负责不同的功能. 链路层, 也成为数据链路层或者网络接口层. 通常包括 操作系统中的设备驱动程序和计算机中对应的网络接口卡. 主 ...

  10. .htaccess伪静态(URL重写)绑定域名到子目录实现子站点

    Apache主机一般支持.htaccess伪静态,即可以实现绑定域名到子目录.一个空间多个站点. 应用举例:绑定htaccess.800m.net到htaccess目录 根目录下.htaccess内容 ...