use函数干了什么
- 先看看源码,忽略掉容错的判断代码,其实就是做了一个push的动作,把我们自己写的fn函数push到this.middleware中,
app.use = function(fn){
if (!this.experimental) {
// es7 async functions are not allowed,
// so we have to make sure that `fn` is a generator function
assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function');
}
debug('use %s', fn._name || fn.name || '-');
this.middleware.push(fn);
return this;
};
middleware是怎样运行的
- 首先koa中application.js是初始执行文件,暴露了一个Application构造函数,一般我们new Koa()时构造函数内部会初始化一些属性。上节讲到的middleware 数组也在构造函数的初始化属性中,下面是源码片段
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);
}
- 接着就是listen函数,listen是绑定在Application构造函数prototype(原型)上的,下面看看listen做了什么, 创建了node的一个原生server服务器。重点看看this.callback函数内部做的了些什么,因为我们知道客户端有请求进来都会触发http.createServer中的回调函数,也就是这里的this.callback
app.listen = function(){
debug('listen');
var server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
};
- callback函数内部做了什么了,看重点部分,this.experimental应该是判断是否支持ES7,重点看下 co.wrap(compose(this.middleware))这句代码做了什么,首先是compose,也就是koa项目依赖的一个包koa-compose,(这里提一下,koa1.x依赖的是koa-compose2.x这个版本)
- 我们看看compose都做了些什么,其实koa-compose中就是这短短的20多行代码,compose返回一个generator函数,传入一个参数next
- 第一步判断next有没有,没有的给个默认值空的generator对象(也就是noop,noop调用后会方法一个generator对象)
- 第二步就是获取middleware数组长度
- 第三步倒序循环middleware数组,依次调用数组里的函数,并且把上一次的middleware(中间件)执行后的generator对象,作为参数传入下一个中间件(也就是next,next记录上一次function* () {}执行后的generator对象)
- 最后一步就是return yield *next,这一步很巧妙的应用的yield 语法的特性,因为是倒序循环,最后返回的就是第一个middleware函数调用后返回的generator对象。
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 handleRequest(req, res){
var ctx = self.createContext(req, res);
self.handleRequest(ctx, fn);
}
};
function compose(middleware){
return function *(next){
if (!next) next = noop();
var i = middleware.length;
while (i--) {
next = middleware[i].call(this, next);
}
return yield *next;
}
}
/**
* Noop.
*
* @api private
*/
function *noop(){}
小结 :执行次序,compose(this.middleware)返回一个 -> generator函数 -> generator函数内主要做了middleware嵌套处理和嵌套关系,结论是调用compose后我们拿到一个generator函数
tj大神的co库(核心部分)
-
继续接上一节,co.wrap()拿到的参数是一个generator函数,现在我们进co库的源码里看看
-
co.wrap()返回了一个叫createPromise函数,createPromise函数内部调用执行了co函数(co库的核心),并且把刚刚compose返回的那个generator函数调用fn.apply(this, arguments)后传入co函数。
co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
function createPromise() {
return co.call(this, fn.apply(this, arguments));
}
};
- 接下来重点来了,co函数做了什么操作,co函数传入一个参数,这个参数正是刚刚上面提到的,compose返回的generator函数调用fn.apply(this, arguments)后generator对象(gen),co函数返回一个Promise,主要看下Promise回调函数内部代码,首先做了些容错的判断(可以先忽略掉),接下来就是执行onFulfilled函数,onFulfilled内部主要做了两件事,gen.next(res)就是执行我们middleware的yield
function co(gen) {
var ctx = this;
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);
return null;
}
/**
* @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) {
if (ret.done) return resolve(ret.value);
var value = 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) + '"'));
}
});
}
- next(ret)调用next函数,next函数内部首先做了一个判断如果是最后一个yield就直接返回成功,接着就是调用toPromise函数,我看看toPromise函数做了什么,主要做了些Promise类型的判断,重点是这句 if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
function toPromise(obj) {
if (!obj) return obj;
if (isPromise(obj)) return obj;
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
if ('function' == typeof obj) return thunkToPromise.call(this, obj);
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
if (isObject(obj)) return objectToPromise.call(this, obj);
return obj;
}
- 如果是generator就在重新执行一遍co函数,这里也就是递归调用了。
- 一般我们middleware函数会这么些
app.use(function* f1(next) {
console.log('f1: pre next');
yield next;
});
这里 yield next就是走到co库的
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj)这段代码来,
在重新走遍co函数的逻辑,也就是执行权交个下个中间件
- 最后我们看看这句代码
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
判断如果返回的是一个Promise,就做成功和错误的处理回调,就是为啥传入的中间件函数,yield后面要返回的是一个Promise了,如果成功就重新调用onFulfilled,在执行下个gen.next(),这也就是为啥generator函数可以自动向下执行的原因了,我知道的generator函数是必须的自己手动gen.next()才依次向下继续执行的。
- 上传代码理解下
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
});
总结
- 其实就是通过generator来暂停函数的执行逻辑来实现等待中间件的效果,通过监听promise来触发继续执行函数逻辑,所谓的回逆也不过就是同步执行了下一个中间件罢了。 第一个中间件代码执行一半停在这了,触发了第二个中间件的执行,第二个中间件执行了一半停在这了,触发了第三个中间件的执行,然后,,,,,,第一个中间件等第二个中间件,第二个中间件等第三个中间件,,,,,,第三个中间件全部执行完毕,第二个中间件继续执行后续代码,第二个中间件代码全部执行完毕,执行第一个中间件后续代码,然后结束
盗了一张图