本笔记共四篇
Koa源码阅读笔记(1) -- co
Koa源码阅读笔记(2) -- compose
Koa源码阅读笔记(3) -- 服务器の启动与请求处理
Koa源码阅读笔记(4) -- ctx对象javascript
在7月23号时,我参加了北京的NodeParty。其中第一场演讲就是深刻讲解Koa。
因为演讲只有一个小时,讲不完Koa的原理。因而在听的时候以为并非很知足,遂开始本身翻看源代码。
而Koa1是基于ES6的generator
的。其在Koa1中的运行依赖于co。
正好本身以前也想看co的源代码,因此趁着这个机会,一口气将其读完。前端
关于co,其做者的介绍非常简单。java
The ultimate generator based flow-control goodness for nodejs (supports thunks, promises, etc)node
而co的意义,则在于使用generator
函数,解决了JavaScript的回调地狱问题。git
co的源代码十分简洁,一共才两百余行。并且里面注释到位,因此阅读起来的难度仍是不大的。
co的核心代码以下(已加上本身的注释):es6
/** * Execute the generator function or a generator * and return a promise. * * @param {Function} fn * @return {Promise} * @api public */ 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) { // 启动generator函数。 if (typeof gen === 'function') gen = gen.apply(ctx, args); // 若是gen不存在或者gen.next不是函数(非generator函数)则返回空值 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 return的对象 // gen.next(res),则是向generator函数传参数,做为yield的返回值 /** * yield句自己没有返回值,或者说老是返回undefined。 * next方法能够带一个参数,该参数就会被看成上一个yield语句的返回值。 * [next方法的参数](http://es6.ruanyifeng.com/#docs/generator#next方法的参数) */ ret = gen.next(res); } catch (e) { return reject(e); } // 在这儿,每完成一次yield,便交给next()处理 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) { // 若是这个generator函数完成了,返回最终的值 // 在全部yield完成后,调用next()会返回{value: undefined, done: true} // 因此须要手动return一个值。这样最后的value才不是undefined if (ret.done) return resolve(ret.value); // 未完成则统一交给toPromise函数去处理 // 这里的ret.value实际是 yield 后面的那个(对象|函数|值) 好比 yield 'hello', 此时的value则是 'hello' var value = toPromise.call(ctx, ret.value); // 这里value.then(onFulfilled, onRejected),实际上已经调用并传入了 onFulfilled, onRejected 两个参数。 // 由于非这些对象,没法调用then方法。也就没法使用onFulfilled 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) + '"')); } }); } /** * 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; }
看完了源代码,对generator
函数有更深的理解,也理解了co的运行机制。github
首先解决的问题则是自动执行generator
函数是如何实现的。
这儿的核心部分则在于:segmentfault
function co(gen) { if (typeof gen === 'function') gen = gen.apply(ctx, args); onFulfilled(); function onFulfilled(res) { var ret; try { ret = gen.next(res); } catch (e) { return reject(e); } next(ret); } 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传入一个generator
函数后,co会将其自动启动。而后调用onFulfilled
函数。
在onFulfilled
函数内部,首先则是获取next的返回值。交由next
函数处理。
而next
函数则首先判断是否完成,若是这个generator函数完成了,返回最终的值。
不然则将yield
后的值,转换为Promise
。
最后,经过Promise
的then,并将onFulfilled
函数做为参数传入。api
if (value && isPromise(value)) { return value.then(onFulfilled, onRejected); }
而在generator
中,yield
句自己没有返回值,或者说老是返回undefined
。
而next方法能够带一个参数,该参数就会被看成上一个yield
语句的返回值。
同时经过onFulfilled
函数,则能够实现自动调用。
这也就能解释为何co基于Promise
。且能自动执行了。promise
co的源代码读取来不难,但其处理方式却使人赞叹。
并且generator
函数的使用,对ES7中的Async/Await
的产生,起了关键做用。
正如其做者TJ在co的说明文档中所说的那样:
Co is a stepping stone towards ES7 async/await.
虽说我没用过co,只使用过Async/Await
。
但现在的Async/Await
,使用babel,启用transform-async-to-generator插件,转译后,也是编译为generator
函数。
因此了解一下,仍是有好处的。并且阅读co的源代码,是阅读koa1源码的必经之路。
前端路漫漫,且行且歌。
最后附上本人博客地址和原文连接,但愿能与各位多多交流。