连接来源javascript
Request
,Context
,Response 在代码运行以前就已经存在的
java
Request和Response自身的方法会委托到Context中。node
Context源码片断git
var delegate = require('delegates'); var proto = module.exports = {}; // 一些自身方法,被我删了 /** * Response delegation. */ delegate(proto, 'response') .method('attachment') .method('redirect')
能够看到, context 把 response 的方法加到 context本身的原型中来, 方便用户直接经过context调用 response request的方法github
delegate实现过程, 先在构造函数中存储context的原型 proto, 再method中更加name 把 response原型的方法追加到 context中json
function Delegator(proto, target) { if (!(this instanceof Delegator)) return new Delegator(proto, target); this.proto = proto; this.target = target; this.methods = []; this.getters = []; this.setters = []; this.fluents = []; } Delegator.prototype.method = function(name){ var proto = this.proto; var target = this.target; this.methods.push(name); proto[name] = function(){
//Response[name].apply(Response, arguments); 至关下面 return this[target][name].apply(this[target], arguments); }; return this; };
上面是委托了 Response Request的方法, 其实context还委托了 Response request的属性名称, api
能够用context直接访问他们的属性的值, 用context设置属性的值的时候, Response request的属性值也会改变promise
分别是用getter setter实现的服务器
Delegator.prototype.getter = function(name){ var proto = this.proto; var target = this.target; this.getters.push(name); proto.__defineGetter__(name, function(){
//Response[name] return this[target][name]; }); return this; };
咱们在来看看accesscookie
Delegator.prototype.access = function(name){ return this.getter(name).setter(name); };
能够看到,这个方法是getter+setter
应用启动前的内容到如今就说完了
接下来咱们看看使用 koa来启动一个app的时候,koa内部会发生什么呢?
会通过两步
第一步是 new 一个 koa 对象app,
第二步是用 app对象 监听端口号,
// 第一步 - 初始化app对象 var koa = require('koa'); var app = koa(); // 第二步 - 监听端口 app.listen(1995);
能够看一下源码 koa() 这里发生了什么
module.exports = Application; function Application() {
if (!(this instanceof Application)) return new Application; this.env = process.env.NODE_ENV || 'development'; this.subdomainOffset = 2;
this.middleware = []; this.proxy = false;
this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); }
能够看到, 这里只是定义了一个空的中间件对象, 并把context request等的实例挂载到application中
这里应为实例化了context, 因此会执行delegate方法,因此这一步, 也是 正式把request response委托到context上
到了这里并没启动server, 直到监听的时候
app.listen = function(){
debug('listen'); var server = http.createServer(this.callback()); return server.listen.apply(server, arguments);
};
能够看到,在执行app.listen(1995)
的时候,启动了一个server,而且监听端口。
熟悉nodejs的同窗知道http.createServer接收一个函数做为参数,每次服务器接收到请求都会执行这个函数,
这个函数会传入两个参数(request和response,简称req和res),那么如今重点在this.callback
这个方法上。
好比 http.createServer( funciton(res, req){ req.send('text') } //callback),
koa利用 listen 包装了这个函数, 使用listen直接启动服务, 并把callback包装一下 ,注入本身的东西, 这里其实就是初始化了一些中间件
为何要初始化呢, 咱们是使用app.use(log)添加一个日志中间件, 这里只是push一个中间件, 而初始化就是使这些中间件联系起来
app.callback 源码
app.callback = function(){
//咱们能够指定 app.experimental = true, 这样就开启了es7的一些功能, 好比咱们可使用 async await 了 if (this.experimental) { console.error('Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.') }
//这里分析的时候简化为 co.wrap(compose(this.middleware)); 由于两个分支的核心思想是同样的, es7更简化了而已 var fn = this.experimental ? compose_es7(this.middleware) : co.wrap(compose(this.middleware));
var self = this; if (!this.listeners('error').length) this.on('error', this.onerror); return function(req, res){
res.statusCode = 404; var ctx = self.createContext(req, res); onFinished(res, ctx.onerror);
fn.call(ctx).then(function () { respond.call(ctx); }).catch(ctx.onerror);
}
};
var fn = co.wrap(compose(this.middleware));
虽然只剩下一行代码,但不要小瞧它哦~~
咱们先看这部分,的全名叫,他的做用是把一个个不相干的中间件串联在一块儿。
compose(this.middleware)composekoa-compose
// 有3个中间件 this.middlewares = [function *m1() {}, function *m2() {}, function *m3() {}]; // 经过compose转换 var middleware = compose(this.middlewares); // 转换后获得的middleware是这个样子的 function *() { yield *m1(m2(m3(noop()))) }
相似:
function a(s){ console.log(s) }
function b(s){console.log(s); return s+1}
function c(s){console.log(s); return s+2}
a( b(c(0)) );
上面这个函数执行的时候遇到 yield, 因此 *m1不会执行, 当调用next后, 执行顺序 noop() => m3() => m2( ) => m1()
yield *m1(m2(m3(noop())))
但m3是一个generator函数, 因此它第一次执行, 实际上里面的代码是不会执行的, 直到你调用 next,
因此当你把 m3() 执行后返回的一个对象做为m2的参数时, 这个参数暂时命名为 p1, 使得有条件在m2中调用 p1.next
来真正执行m3里面的代码 , 没错,这就是大名鼎鼎的 中间件 next
咱们是这样利用中间件的
var app = require('koa')(),
router = require('koa-router')(); app.use(function *(next){ console.log(1); yield next; console.log(5); }); app.use(function *(next){ console.log(2);
//实际上这个next 就是m3第一次执行完之后的generator对象, 当console.log(2)执行完后, 再koa会调用app.middleware.next方法, 则把yield后面的 next执行了 yield next; console.log(4); }); app.use(function *(){ console.log(3); }); app.listen(3000);
当一个请求到达的时候,控制台会依次输出1 2 3 4 5
,这就是koa中强大的middleware特性
能够中断当前代码的执行, 去执行下一个中间件B的代码, 等下一个中间件B的代码执行完成, 再去执行剩余的,
同时B也能够中断其执行下一个中间件C, 从而使得这些中间件能够窜起来使用, 一个中间件内部直接利用另外一个中间件的结果,
问题:
若是另外一个中间件是异步? 估计也会内部增长一个yield, 多调用一次next拿到异步数据, + 哦, 后面会解释道, 好像是利用promise 保证异步完成, 才调用返回上一个中间件的.
执行如下代码, 能够更了解 yield特性
function* numbers() { console.log('function start.'); var v1 = yield 0; console.log('v1 = ' + v1); var v2 = yield 1; console.log('v2 = ' + v2); return 5; } var nums = numbers();
//nums.next(); nums.next(); nums.next();
compose 实现过程/** * 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){
return function *(next){
if (!next) next = noop(); var i = middleware.length;
//先把中间件从后往前依次执行
while (i--) {
//把每个中间件执行后获得的 generator对象 赋值给变量next next = middleware[i].call(this, next); } return yield *next; } } /** * Noop. * * @api private */ function *noop(){}
最后,有一个很是巧妙的地方,就是最后一行return yield *next;
这行代码能够实现把compose
执行后 return的函数变成第一个中间件,(由于第一个中间件其实在while循环中执行了一次)
也就是说,执行compose
以后会获得一个函数,执行这个函数就与执行第一个中间件的效果是如出一辙的,
这主要依赖了generator函数的yield *语句的特性。
咱们接着说刚才没说完的
var fn = co.wrap(compose(this.middleware));
上面这段代码如今就能够理解成下面这样
var fn = co.wrap(function *() {yield *m1(m2(m3(noop())))});
co是TJ大神基于Generator开发的一款流程控制模块,白话文就是:就是把异步变成同步的模块。。。
看下源码
co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn; return createPromise;
function createPromise() { return co.call(this, fn.apply(this, arguments)); } };
从源码中能够看到,它接收一个参数,这个参数就是可用状态下的中间件,
返回一个函数createPromise,当执行createPromise这个函数的时候,
调用co并传入一个参数,这个参数 fn 是中间件函数执行后生成的 Generator对象 。
这意味着,返回的这个函数 createPromise 是 触发执行中间件逻辑的关键,一旦这个函数被执行,那么就会开始执行中间件逻辑
回到callback代码
app.callback = function(){
if (this.experimental) { console.error('Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.') }
var fn = this.experimental ? compose_es7(this.middleware) : co.wrap(compose(this.middleware)); var self = this; if (!this.listeners('error').length) this.on('error', this.onerror);
//最后的等待函数 return function(req, res){
res.statusCode = 404; var ctx = self.createContext(req, res); onFinished(res, ctx.onerror);
//执行这里 实际上就是执行了createPromise函数, 也就是执行了第一个中间件 fn.call(ctx).then(function () { respond.call(ctx); }).catch(ctx.onerror); } };
从源码中,能够看到这个函数赋值给fn,fn是在下面那个函数中执行的,下面那个函数是接下来要说的内容~
到如今,咱们的koa已经处于一种待机状态,全部准备都以准备好(中间件和context),万事俱备,只欠东风。。。。。。
东风就是request请求~~
前面说了启动前的一些准备工做和启动时的初始化工做,如今最后一步就是接收请求的时候,
koa要作的事情了,这部分也是koa中难度最大的一部分。不过认真阅读下去会有收获的。。
上面咱们说this.callback
这个方法有两个部分,第一个部分是初始化中间件,而另外一部分就是接收请求时执行的函数啦。
简单回顾下
// 建立server并监听端口 app.listen = function(){ debug('listen'); var server = http.createServer(this.callback());
return server.listen.apply(server, arguments); }; // 这个方法返回的函数会被传递到http.createServer中,
// http.createServer这个方法的做用是 : 每当服务器接收到请求的时候,都会执行第一个参数,而且会传递request和response
// listen 服务器是怎么处理的, 如何传resp resq 得深刻到底层了
app.callback = function(){ if (this.experimental) { console.error('Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.') } var fn = this.experimental ? compose_es7(this.middleware) : co.wrap(compose(this.middleware)); var self = this; if (!this.listeners('error').length) this.on('error', this.onerror); return function(req, res){
res.statusCode = 404; var ctx = self.createContext(req, res); onFinished(res, ctx.onerror);
fn.call(ctx).then(function () { respond.call(ctx); }).catch(ctx.onerror);
} };
因此第二部分的重点就是下面段代码啦~
return function(req, res){
res.statusCode = 404; var ctx = self.createContext(req, res); onFinished(res, ctx.onerror);
fn.call(ctx).then(function () { respond.call(ctx); }).catch(ctx.onerror);
}
咱们先看这段代码
var ctx = self.createContext(req, res);
不知道各位童鞋还记不记得文章一开始的时候那个整体流程图下面的那个相似于八卦同样的东西???
这行代码就是建立一个最终可用版的context。
从上图中,能够看到分别有五个箭头指向ctx,表示ctx上包含5个属性,分别是request,response,req,res,app。
request和response也分别有5个箭头 指向它们,因此也是 一样的逻辑。
这里须要说明下
var ctx = self.createContext(req, res);
很少说,我们观摩下代码
app.createContext = function(req, res){ // 继承 var context = Object.create(this.context); var request = context.request = Object.create(this.request); var response = context.response = Object.create(this.response); // 往context,request,response身上挂载属性 实现上图 的关系 效果 context.app = request.app = response.app = this; context.req = request.req = response.req = req; context.res = request.res = response.res = res;
request.ctx = response.ctx = context; request.response = response; response.request = request;
context.onerror = context.onerror.bind(context); context.originalUrl = request.originalUrl = req.url;
context.cookies = new Cookies(req, res, { keys: this.keys, secure: request.secure });
context.accept = request.accept = accepts(req); context.state = {}; // 最后返回完整版context return context;
};
讲到这里其实我能够很明确的告诉你们,,,koa中的this
其实就是app.createContext
方法返回的完整版context
又因为这段代码的执行时间是接受请求的时候,因此代表每一次接受到请求,都会为该请求生成一个新的上下文context
上下文到这里咱们就说完啦。咱们接着往下说,看下一行代码
onFinished(res, ctx.onerror);
这行代码其实很简单,就是监听response,若是response有错误,
会执行ctx.onerror
中的逻辑,设置response类型,状态码和错误信息等。
源码以下:
onerror: function(err){ // don't do anything if there is no error. // this allows you to pass `this.onerror` // to node-style callbacks. if (null == err) return; if (!(err instanceof Error)) err = new Error('non-error thrown: ' + err); // delegate this.app.emit('error', err, this); // nothing we can do here other // than delegate to the app-level // handler and log. if (this.headerSent || !this.writable) { err.headerSent = true; return; } // unset all headers this.res._headers = {}; // force text/plain this.type = 'text'; // ENOENT support if ('ENOENT' == err.code) err.status = 404; // default to 500 if ('number' != typeof err.status || !statuses[err.status]) err.status = 500; // respond var code = statuses[err.status]; var msg = err.expose ? err.message : code; this.status = err.status; this.length = Buffer.byteLength(msg); this.res.end(msg); }
咱们接着说,还有最后一个知识点,也是本章最复杂的知识点,关于中间件的执行流程,这里会说明为何koa的中间件能够回逆。
咱们先看代码
咱们先看代码
fn.call(ctx).then(function () { respond.call(ctx); }).catch(ctx.onerror);
co.wrap
返回的那个函数整体上来讲,执行fn.call(ctx)
会返回promise,koa会监听执行的成功和失败,成功则执行respond.call(ctx);
,失败则执行ctx.onerror
,
失败的回调函数刚刚已经讲过。这里先说说respond.call(ctx);
。
咱们在写koa的时候,会发现全部的response操做都是
this.body = xxx;
this.status = xxxx;
这样的语法,但若是对原生nodejs有了解的童鞋知道,
nodejs的response只有一个api那就是res.end();
,而设置status状态码什么的都有不一样的api,
那么koa是如何作到经过this.xxx = xxx
来设置response的呢?
先看一张图,,我盗的图
从图中看到,request请求是以respond结束的。
是滴,全部的request请求都是以respond这个函数结束的,
这个函数会 读取this.body中的值 根据不一样的类型来决定以什么类型响应请求
咱们来欣赏一下源码
function respond() { // allow bypassing koa if (false === this.respond) return; var res = this.res; if (res.headersSent || !this.writable) return; var body = this.body; var code = this.status; // ignore body if (statuses.empty[code]) { // strip headers this.body = null; return res.end(); } if ('HEAD' == this.method) { if (isJSON(body)) this.length = Buffer.byteLength(JSON.stringify(body)); return res.end(); } // status body if (null == body) { this.type = 'text'; body = this.message || String(code); this.length = Buffer.byteLength(body); return res.end(body); } // responses if (Buffer.isBuffer(body)) return res.end(body); if ('string' == typeof body) return res.end(body); if (body instanceof Stream) return body.pipe(res); // body: json body = JSON.stringify(body); this.length = Buffer.byteLength(body); res.end(body); }
仔细阅读的童鞋会发现,咦,,,,为毛没有设置status和header等信息的代码逻辑?
这不科学啊。我分明记得状态码是rs.statusCode = 400
这样设置的,为啥代码中没有??
这就要从最开始的上下文提及了。为何Response静态类中添加req和res属性?
就是由于添加了req和res以后,response和request类就能够直接操做req和res啦。。咱们看一段源码就明白了
set status(code) {
assert('number' == typeof code, 'status code must be a number'); assert(statuses[code], 'invalid status code: ' + code); this._explicitStatus = true;
this.res.statusCode = code; this.res.statusMessage = statuses[code]; if (this.body && statuses.empty[code]) this.body = null;
},
主要是this.res.statusCode = code;
this.res.statusMessage = statuses[code];
这两句,
statusCode
和statusMessage
都是nodejs原生api。有兴趣能够自行查看~
接下来咱们开始说说koa的中间件为何能够回逆,为何koa的中间件必须使用generator,yield next又是个什么鬼?
咱们看这段代码
fn.call(ctx)
fn刚刚上面说过,就是co.wrap
返回的那个函数,上面也说过,一旦这个函数执行,就会执行中间件逻辑,而且经过.call
把ctx
设为上下文,也就是this。
那中间件逻辑是什么样的呢。咱们先看一下源码:
co.wrap = function (fn) { createPromise.__generatorFunction__ = fn; return createPromise;
function createPromise() { return co.call(this, fn.apply(this, arguments)); }
};
先回顾下,createPromise就是fn,每当执行createPromise的时候,都会执行co,
中间件是基于co实现的、因此咱们接下来要说的是co的实现逻辑。
而执行co所传递的那个参数,咱们给它起个名,就叫中间件函数
吧,中间件函数也是一个generator函数,
由于在执行co的时候执行了这个中间件函数,因此实际上真正传递给co的参数是一个generator对象,为了方便理解,咱们先起个名叫中间件对象吧
那咱们看co的源码:
function co(gen) {
var ctx = this;
//获取 gen 后面的参数 var args = slice.call(arguments, 1) // we wrap everything in a promise to avoid promise chaining, // which leads to memory leak errors. // see https://github.com/tj/co/issues/180
return new Promise(function(resolve, reject) { if (typeof gen === 'function') gen = gen.apply(ctx, args); if (!gen || typeof gen.next !== 'function') return resolve(gen); onFulfilled(); /** * @param {Mixed} res * @return {Promise} * @api private */ function onFulfilled(res) { var ret; try { ret = gen.next(res); } catch (e) { return reject(e); } next(ret); } /** * @param {Error} err * @return {Promise} * @api private */ function onRejected(err) { var ret; try { ret = gen.throw(err); } catch (e) { return reject(e); } next(ret); } /** * Get the next value in the generator, * return a promise. * * @param {Object} ret * @return {Promise} * @api private */ function next(ret) {
//若是一个中间件已经完成全部的 yield, 回到上一个中间件继续执行 if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
//若是还有yield 则继续调用 then方法, 其实会调用next方法, 执行剩余的 yield
//if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); <==> tomPromise.call
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' + 'but the following object was passed: "' + String(ret.value) + '"')); } }); }
能够看到,代码并非不少。
首先执行co会返回一个promise,koa会对这个promise的成功和失败都准备了不一样的处理,上面已经说过。
咱们在看这段代码
function onFulfilled(res) { var ret;
try {
//按顺序执行, 从第一个中间件开始, 遇到 yield next 才执行下一个 ret = gen.next(res); } catch (e) { return reject(e); }
next(ret);
}
这个函数最重要的做用是运行gen.next
来 执行 中间件中的 业务逻辑。
一般在开发中间件的时候会这样写
yield next;
因此ret中包含下一个中间件对象
(还记得上面咱们初始化中间件的时候中间件的参数是什么了吗??)
而后把下一个中间件对象传到了next(ret)
这个函数里,next函数是干什么的?咱们看看
function next(ret) {
//若是中间件已经结束(没有yield了),那么调用promise的resolve。 if (ret.done) return resolve(ret.value);
//不然的话把ret.value(就是下一个中间件对象),用co在包一层 var value = toPromise.call(ctx, ret.value); //if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected( new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"')); }
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
上面是toPromise中的一段代码
既然是用co又执行了一遍,那么co是返回promise的。因此返回的 这个value就分别被监听了 成功和失败 的不一样处理。
value.then(onFulfilled, onRejected);
因此咱们能够看到,若是第二个中间件里依然有yield next
这样的语句,那么第三个中间件依然会被co包裹一层并运行.next方法,依次列推,这是一个递归的操做
因此咱们能够确定的是,每个中间件都被promise包裹着,直到有一天中间件中的逻辑运行完成了,那么会调用promise的resolve来告诉程序这个中间件执行完了。
那么中间件执行完了以后,会触发onFulfilled
,这个函数会执行.next方法。
前方高能预警!!!
好比有3个中间件,当系统接收到请求的时候,会执行co,co会马上执行onFulfilled来调用.next往下执行,
将获得的返回结果(第二个中间件的generator对象,上面咱们分析过)传到co中在执行一遍。以此类推,一直运行到最后一个yield,
这个时候系统会等待中间件的执行结束,一旦最后一个中间件执行完毕,会马上调用promise的resolve方法表示结束。
(这个时候onFulfilled函数的第二个执行时机到了,这样就会出现一个现象,
一个generator对象的yield只能被next一次,下次执行.next的时候从上一次停顿的yield处继续执行,
因此如今当有一个中间件执行完毕后,在执行.next就会在前一个中间件的yield处继续执行)
当最后一个中间件执行完毕后,触发promise的resolve,而别忘了,第二个中间件但是用then监听了成功和失败的不一样处理方法,
一旦第三个中间件触发成功,第二个中间件会马上调用onFulfilled来执行.next,
继续从第二个中间件上一次yield停顿处开始执行下面的代码,而第二个中间件的逻辑执行完毕后,
一样会执行resolve表示成功,而这个时候第一个中间件正好也经过.then方法监听了第二个中间件的promise,
也会马上调用onFulfilled函数来执行.next方法,这样就会继续从第一个中间件上一次yield的停顿处继续执行下面的逻辑,以此类推。
这样就实现了中间件的回逆,经过递归从外到里执行一遍中间件,而后在经过promise+generator从里往外跳。
因此有一个很是重要的一点须要注意,onFulfilled
这个函数很是重要,重要在哪里???重要在它执行的时间上。
onFulfilled
这个函数只在两种状况下被调用,一种是调用co的时候执行,还有一种是当前promise中的全部逻辑都执行完毕后执行
其实就这一句话就能说明koa的中间件为何会回逆。
因此若是咱们在一个中间件中写好多yield,就能够看出关键所在,
先经过递归从外往里(从第一个中间件运行到最后一个中间件)每次遇到yield next就会进入到下一个中间件执行,
当运行到最后发现没有yield的时候,会跳回上一个中间件继续执行yield后面的,结果发现又有一个yield next,
它会再次进入到下一个中间件,进入到下一个中间件后发现什么都没有,
由于yield的特性(一个generator对象的yield只能被next一次,下次执行.next的时候从上一次停顿的yield处继续执行),
因此便又一次跳入上一个中间件来执行。以此类推。
咱们试一下:
var koa = require('koa'); var app = koa(); app.use(function* f1(next) { console.log('f1: pre next'); yield next; console.log('f1: post next'); yield next; console.log('f1: fuck'); }); app.use(function* f2(next) { console.log(' f2: pre next'); yield next; console.log(' f2: post next'); yield next; console.log(' f2: fuck'); }); app.use(function* f3(next) { console.log(' f3: pre next'); yield next; console.log(' f3: post next'); yield next; console.log(' f3: fuck'); }); app.use(function* (next) { console.log('hello world') this.body = 'hello world'; }); app.listen(3000);
上面的代码打印的log是下面这样的
f1: pre next
f2: pre next f3: pre next hello world f3: post next f3: fuck f2: post next f2: fuck f1: post next f1: fuck
那么我用白话文来讲一下中间件的逻辑,大概是这样的, 就是前面说的
第一个中间件代码执行一半停在这了,触发了第二个中间件的执行,第二个中间件执行了一半停在这了,触发了第三个中间件的执行,
而后,,,,,,第一个中间件等第二个中间件,第二个中间件等第三个中间件,,,,,,第三个中间件所有执行完毕,
第二个中间件继续执行后续代码,第二个中间件代码所有执行完毕,执行第一个中间件后续代码,而后结束
用一张图表示大概是这样的。
为了方便理解,伪代码大概是下面这样
new Promise(function(resolve, reject) { // 我是中间件1 yield new Promise(function(resolve, reject) { // 我是中间件2 yield new Promise(function(resolve, reject) { // 我是中间件3 yield new Promise(function(resolve, reject) { // 我是body }); // 我是中间件3 }); // 我是中间件2 }); // 我是中间件1 });
这就是最核心的思想!!
最后这句话解释的思想好像文章的前半部分均可以看出来了, 我以为 难点在于若是后面的中间件也是异步的,调用了多个 yield ... 实现异步操做,
如何处理 .. 也是是利用了promise把generator进一步封装... 等下一个中间件把yield执行完, 在调用resolve 去回到上一个中间件去执行剩余的代码
简单总结一下,其实也很简单,只是第一次接触的同窗可能暂时没有理解透彻。
其实就是经过generator来暂停函数的执行逻辑来实现等待中间件的效果,经过监听promise来触发继续执行函数逻辑,所谓的回逆也不过就是同步执行了下一个中间件罢了。
好比有几个中间件,mw1,mw2,mw3,mwn...
站在mw1的角度上看,它是不须要关系mw2里面有没有mw3,它只须要关心mw2什么时候执行完毕便可,当mw2执行完毕mw1继续执行yield以后的代码逻辑。其实很简单,callback也是这个原理,当mw2执行完毕执行下callback,mw1是不须要关心mw2里面到底是怎样运行的,只要知道mw2执行完会执行回调就好了。mw2也是一样的道理不须要关心mw3。
到这里,关于koa咱们就已经差很少都说完了。固然还有一些细节没有说,好比koa中的错误处理,但其实都是小问题啦,关于generator的错误处理部分弄明白了,天然就明白koa的错误处理是怎样的。这里就不在针对这些讲述了,一次写这么多确实有点累,或许后期会补充进来吧。。
最后,若是认真阅读下来的同窗能感受出来,koa中有两个最重要的地方,不管是使用上,仍是思想上,这两个点都很是重要,koa也只有这两个概念