koa1.x
上一篇简要分析了koa2的源码,这一篇分析koa1的源码,koa1主要推荐使用generator function的中间件写法,由于generator function不具备自执行能力,所以还引入了tj大神的co库,我看的koa版本是1.2.5
generator
generator是es2015中定义的一个新特性,已经写入到规范中。用法如下
1 2 3 4 5 6 7 8 9 10 11
| function* idMaker() { var index = 0; while (index < 2) yield index++; } var gen = idMaker(); console.log(gen.next()); console.log(gen.next()); console.log(gen.next());
|
generator函数一般和yield配合使用。idMaker()调用函数的时候并不是立即执行函数,而是类似返回一个内部的指针,调用next内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。
这里每次next方法的返回值中的value都是yield表达式的值,done表示内部流程是否执行完成。
这里对比async函数可以发现两个问题:
- generator没有自执行能力,需要不断调用next方法
- next方法的调用时机要自己把握,才能完成异步操作;不像await,可以直接接受promise,通过等待promise状态改变,就很方便的执行一个个异步操作。
从这里看,确实async函数要方便很多
koa中间件
回到koa1.x上面,给个例子说下,如何编写中间件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| var koa = require('koa'); var app = koa(); app.use(function *(next){ var start = new Date; yield next; var ms = new Date - start; this.set('X-Response-Time', ms + 'ms'); }); app.use(function *(next){ var start = new Date; yield next; var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); }); app.use(function *(){ this.body = 'Hello World'; }); app.listen(3000); console.log("listen on 3000");
|
这里的执行流程和koa2是一致的。把中间件想作一个栈,请求会从顶部的第一个中间件开始处理,遇到yield next调用,就会进入下一个中间件中,直到最后没有yield next调用,再从栈底反弹,一个一个执行之前next之后的代码。
koa源码
koa源码不长,也很简洁漂亮,先看package.json,”main”:”lib/application.js”表明入口就是application.js,这里简单截取了几个重要的代码片段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| * Application prototype. */ var app = Application.prototype; * Expose `Application`. */ module.exports = Application; * Initialize a new `Application`. * * @api public */ 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); } * Inherit from `Emitter.prototype`. */ Object.setPrototypeOf(Application.prototype, Emitter.prototype); app.listen = function(){ debug('listen'); var server = http.createServer(this.callback()); return server.listen.apply(server, arguments); }; app.use = function(fn){ if (!this.experimental) { 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; }; 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){ res.statusCode = 404; var ctx = self.createContext(req, res); onFinished(res, ctx.onerror); fn.call(ctx).then(function handleResponse() { respond.call(ctx); }).catch(ctx.onerror); } };
|
这里直接exports的就是Application方法。我们使用var app = new koa(),可见app就是Application的实例。通过原型继承让Application继承自Emitter。
app.listen(3000)在koa中用来创建httpServer,实际和所有的node服务器框架一样,还是基于http.createServer的封装,这里的参数this.callback()作为参数正好来处理request和response。在callback中,流程会走co.wrap(compose(this.middleware)),middleware中存放的正是使用app.use注册的一个个中间件,在use的api中,直接将多个generator方法push进入middleware数组中。
接着看compose内部代码,compose来源自koa-compose包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| * 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--) { next = middleware[i].call(this, next); } return yield *next; } } * Noop. * * @api private */ function *noop(){}
|
compose执行完成返回了一个generator函数,虽然这个函数没有执行,但是看出来,这里对middleware数组从最后一个元素进行执行,但是generator function的执行只是返回pointer,并不是走内部代码逻辑,所以这里主要就是给每个中间件进行传参,越是前面定义的中间件,next中就包含了其后定义的中间件的pointer(每个middleware的next参数是不同的);架设while第一次执行返回的是lastPointer,那么循环执行结束,最终得到的是firstPointer;最终yield next,实际就是开始运行firstPointer的内部逻辑,整个中间件的串链就开始执行了。
yield 的用法是在一个generator内部执行另一个generator的写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function* g1(){ yield 'a'; yield 'b'; } function* g2(){ yield 'x' yield * g1(); yield 'y' } var g2i = g2(); g2i.next(); g2i.next(); g2i.next(); g2i.next();
|
从这里可以看出整个执行链已经组装完毕,只是等待执行了。下面看下co库的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| module.exports = co['default'] = co.co = co; co.wrap = function (fn) { createPromise.__generatorFunction__ = fn; return createPromise; function createPromise() { return co.call(this, fn.apply(this, arguments)); } }; function co(gen) { var ctx = this; var args = slice.call(arguments, 1) 1.promise对象一旦建立就立即执行 2.then方法返回的是一个新的promise实例,因此可以链式调用 */ 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) { 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) + '"')); } }); }
|
co.wrap执行是直接返回了一个createPromise函数,所以fn.call(ctx)就是直接在执行createPromise。co(gen)中的参数gen就是直接执行的compose返回的匿名generator函数。同样这个匿名generator执行只是返回了它的执行指针,gen就是这个执行指针。
co返回的是一个promise对象,promise对象是创建即执行,typeof gen === ‘function’为false,因为typeof gen===’object’并且typeof gen.next == ‘function’。在onFulfilled中,这个匿名generator真正开始执行,看下next函数中,重点就是toPromise.call(ctx, ret.value),主要就是将yield之后的值转化为promise。ret.value就是yield之后的值。转换成promise之后,再根据then调用下次的gen.next使得匿名generator中的逻辑进一步执行。
实际co就是一个generator函数的自执行器。
toPromise
toPromise函数可以详细看看,代码就在co库中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| * Convert a `yield`ed value into a promise. * * @param {Mixed} obj * @return {Promise} * @api private */ 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; } * Convert a thunk to a promise. * * @param {Function} * @return {Promise} * @api private */ function thunkToPromise(fn) { var ctx = this; return new Promise(function (resolve, reject) { fn.call(ctx, function (err, res) { if (err) return reject(err); if (arguments.length > 2) res = slice.call(arguments, 1); resolve(res); }); }); } * Convert an array of "yieldables" to a promise. * Uses `Promise.all()` internally. * * @param {Array} obj * @return {Promise} * @api private */ function arrayToPromise(obj) { return Promise.all(obj.map(toPromise, this)); } * Convert an object of "yieldables" to a promise. * Uses `Promise.all()` internally. * * @param {Object} obj * @return {Promise} * @api private */ function objectToPromise(obj){ var results = new obj.constructor(); var keys = Object.keys(obj); var promises = []; for (var i = 0; i < keys.length; i++) { var key = keys[i]; var promise = toPromise.call(this, obj[key]); if (promise && isPromise(promise)) defer(promise, key); else results[key] = obj[key]; } return Promise.all(promises).then(function () { return results; }); function defer(promise, key) { results[key] = undefined; promises.push(promise.then(function (res) { results[key] = res; })); } }
|
处理里几种不同的情况
- obj为空,直接返回
- obj是promise直接返回
- obj是generation方法,或者是generator方法执行之后返回的pointer(这里叫generator)generator,就递归调用co方法
- obj是function,这种情况很常见,比如 yield readFile(); readFile是一个耗时操作,这里thunkToPromise改造readFile,返回promise,可以看出这时候readFile必须接受一个function (err, res) {}的参数
- obj是数组,转化为Promise.all
- obj是普通对象,拆分key,value。最后使用Promise.all