本筆記共四篇
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源碼的必經之路。
前端路漫漫,且行且歌。
最後附上本人博客地址和原文連接,但願能與各位多多交流。