本笔记共四篇
Koa源码浏览笔记(1) — co
Koa源码浏览笔记(2) — compose
Koa源码浏览笔记(3) — 服务器の启动与要求处置惩罚
Koa源码浏览笔记(4) — ctx对象
原由
在7月23号时,我参加了北京的NodeParty。个中第一场演讲就是深切解说Koa。
由于演讲只要一个小时,讲不完Koa的道理。因而在听的时刻以为并非很满足,遂最先自身翻看源代码。
而Koa1是基于ES6的generator
的。其在Koa1中的运转依赖于co。
恰好自身之前也想看co的源代码,所以趁着这个时机,一口气将其读完。
co
关于co,其作者的引见非常简朴。
The ultimate generator based flow-control goodness for nodejs (supports thunks, promises, etc)
而co的意义,则在于运用generator
函数,处理了JavaScript的回调地狱题目。
源码解读
co的源代码非常简约,一共才两百余行。而且内里诠释到位,所以浏览起来的难度照样不大的。
co的中心代码以下(已加上自身的诠释):
/**
* 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;
}
co的运转机制
看完了源代码,对generator
函数有更深的明白,也明白了co的运转机制。
自动实行generator
起首处理的题目则是自动实行generator
函数是怎样完成的。
这儿的中心部份则在于:
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
函数作为参数传入。
if (value && isPromise(value)) {
return value.then(onFulfilled, onRejected);
}
而在generator
中,yield
句自身没有返回值,或许说老是返回undefined
。
而next要领能够带一个参数,该参数就会被看成上一个yield
语句的返回值。
同时经由过程onFulfilled
函数,则能够完成自动挪用。
这也就可以诠释为何co基于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源码的必经之路。
前端路漫漫,且行且歌。
末了附上本人博客地址和原文链接,愿望能与列位多多交换。