本文只在我的博客和 SegmentFault 社區我的專欄發表,轉載請註明出處
我的博客: https://zengxiaotao.github.io
SegmentFault 我的專欄: https://segmentfault.com/blog...node
學 nodejs 固然避免不了學習框架,畢竟原生的 API 較底層。最早接觸的是 Koa 。看看官網的描述git
next generation web framework for node.jsgithub
我翻譯一下就是: 基於 node.js 的下一代 web 開發框架。好像很厲害的樣子!koa 是一個輕量級的框架,本質上提供了一個架子,經過 各類中間件的級聯的方式實現特定的功能。koa 藉助 promise 和 generator , 很好解決了異步組合問題。web
那什麼又是 co 。學習 koa 就必定少不了學習 co 模塊。co 模塊能夠將異步解放成同步。co 函數接受一個 generator 函數做爲參數,在函數內部自動執行 yield 。segmentfault
使用的 co 模塊版本號爲 4.6.0數組
首先看一些用於判斷對象類型的函數promise
var slice = Array.prototype.slice; // 對數組 slice 方法的引用
function isObject(val) { return Object == val.constructor; }
這兩個應該就不用說了吧。。。app
function isPromise(obj) { return 'function' == typeof obj.then; }
判斷一個對象是不是一個 promise 實例,判斷的依據也很簡單,根據 「鴨子類型」,判斷這個對象是否有 then 方法框架
function isGenerator(obj) { return 'function' == typeof obj.next && 'function' == typeof obj.throw; }
相似的,判斷一個對象時候是 generator 實例,只需判斷這個對象是否具備 next 方法和 throw 方法。koa
function isGeneratorFunction(obj) { var constructor = obj.constructor; if (!constructor) return false; if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true; return isGenerator(constructor.prototype); }
判斷是不是一個 generator 函數,只需判斷這個函數是不是 GeneratorFunction
函數的實例
以上所講的在以後將 value 包裝成 promise 實例時都會用到。
看一下 co 模塊的輸出部分
module.exports = co['default'] = co.co = co
所以如下三種用法等價
var co = require('co') // (1) var co = require('co').co // (2) var co = require('co').default // (3)
接着就是重頭戲 co
函數了。
function co(gen) { var ctx = this; // 保存函數的執行上下文對象 var args = slice.call(arguments, 1) // 傳給 gen 函數的參數 // 返回一個 promise 實例 return new Promise(function(resolve, reject) { // 根據傳入的 generator 函數生成一個 generator 實例 if (typeof gen === 'function') gen = gen.apply(ctx, args); // 若是生成的 gen 不是一個 generator 實例, // promise 直接變成 resolved 狀態 if (!gen || typeof gen.next !== 'function') return resolve(gen); // 執行 onFulfilled 方法 onFulfilled(); function onFulfilled(res) { var ret; try { // 執行 gen 的 next 方法 ret = gen.next(res); } catch (e) { return reject(e); } // 並將這個值傳入 next 函數 next(ret); } function onRejected(err) { var ret; try { ret = gen.throw(err); } catch (e) { return reject(e); } next(ret); } function next(ret) { // 若是 gen 執行完畢, ret.done 變爲 true ,那麼這個 promise 的實例 // 的狀態天然變成了 resolved if (ret.done) return resolve(ret.value); var value = toPromise.call(ctx, ret.value); // 將 value 從新包裝成一個 promise 實例 // 新返回的 promise 實例的 resolve 方法設置爲 onFulfilled 函數,再次執行 next 方法, 從而實現了自動調用 generator 實例的 next 方法 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 實例的 next 方法。那麼接下來看看 co 是怎麼把一個值轉化爲一個 promise 實例。
function toPromise(obj) { if (!obj) return obj; // 若是傳入的 obj 是假值,返回這個假值 如 undefined , false, null if (isPromise(obj)) return obj; // 若是是 Promise 實例,返回這個 promise 實例 if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); // 若是是 generator 函數或者 一個generator if ('function' == typeof obj) return thunkToPromise.call(this, obj); // 若是是 thunk 函數 if (Array.isArray(obj)) return arrayToPromise.call(this, obj); // 若是是一個數組 if (isObject(obj)) return objectToPromise.call(this, obj); // 若是是一個 plain object return obj; // 若是是原始值,則返回這個原始值。 }
那麼每一個函數依次看下去。
function thunkToPromise(fn) { var ctx = this; // 保存函數上下文對象 // 返回一個 promise 實例 return new Promise(function (resolve, reject) { // 執行傳入的 thunk 函數 // thunk 函數接受一個 回調函數 做爲參數 fn.call(ctx, function (err, res) { // 若是 thunk 函數運行錯誤 // promise 實例的 變爲 rejected 狀態,執行 reject 函數,也就是 co 函數內定義的 onRejected 函數,下同 if (err) return reject(err); // 得到多餘參數 if (arguments.length > 2) res = slice.call(arguments, 1); // promise 狀態變爲 resolved ,執行 resolve 函數,也就是 onFulfilled 函數 resolve(res); }); }); }
因此,總結一下就是說,若是 generator 裏 yield 後面是一個 thunk 函數, 這個 thunk 函數接受一個回調參數做爲參數,co 在這個回調函數裏定義了什麼時候將 promise 的狀態變爲 resolved 或者 rejected ,
function arrayToPromise(obj) { // Promise.all 方法返回一個 新的 promise 實例 // 若是 obj 是一個數組,把每一個元素包裝成一個 promise 實例 // 若是每個 promise 若是都變爲 resolved 狀態 // 那麼返回的新的 promise 實例的狀態變爲 resloved 狀態 // 傳給 resolve 函數的參數爲以前每一個 promise 的返回值所組成的數組 return Promise.all(obj.map(toPromise, this)); }
一樣,若是 obj 是一個數組,也就是 yield 語句後面的表達式的值爲一個數組,那麼就執行 Promise.all 方法, 將數組的每一項都變成一個 promise 實例。
具體方法以下:
使用 toPromise 方法將 obj 數組中的每一項都包裝成一個 promise 實例
若是上一步中的數組中有元素不是 promise 實例,Promise.all 方法將調用 Promise.resolve 方法,將其轉化爲 promise 實例。
Promise.all 方法返回一個新的 promise 實例。
只有 promise 實例數組中的全部實例的狀態都變爲 resolved 狀態時,這個新的 promise 實例的狀態纔會變成 resolved。只要數組中有一個 promise 實例的狀態變爲 rejected ,新的promise 實例狀態也立刻變爲 rejected 。
當返回的新的 promise 實例狀態變爲 resolved 時,傳入其 resolve 函數的參數爲以前數組中每一個 promise 實例調用 resolve 函數的返回值組成的數組。若是返回的新的 promise 的狀態變爲 rejected ,那麼傳給 reject 函數的參數爲數組中的 promise 實例最早變爲 rejected 狀態的那一個執行 reject 函數的返回值。
真繞口,多看幾遍應該就能理解了。
最後來看看若是 ret.value 若是是一個對象,co 模塊是怎麼樣把它變成一個 promise 實例的。
function objectToPromise(obj){ // 定義一個空對象 var results = new obj.constructor(); // 獲取 obj 的所有屬性 var keys = Object.keys(obj); // 用於盛放 每一個屬性值生成的對應的 promise 實例 var promises = []; for (var i = 0; i < keys.length; i++) { var key = keys[i]; var promise = toPromise.call(this, obj[key]); // 根據屬性值生成一個 promise 實例 if (promise && isPromise(promise)) defer(promise, key); else results[key] = obj[key]; } // 經過一個 promise.all 方法返回一個新的實例 return Promise.all(promises).then(function () { return results; // 將 results 做爲 onFulfilled 函數的參數 }); // 函數的做用 // 給 promise 添加 resolve 函數 // 而且把這個 promise 實例推入 promises 數組 function defer(promise, key) { // predefine the key in the result results[key] = undefined; promises.push(promise.then(function (res) { results[key] = res; // 定義promise 實例的 resolve 函數 })); } }
分析完 co 的整個源碼總結一下整個執行的過程。首先,co 函數接受一個 generator 函數,而且在 co 函數內部執行,生成一個 generator 實例。調用 generator 的 next 方法, 對生成的對象的 value 屬性值使用 toPromise 方法,生成一個 promise 實例,當這個 promise 實例的狀態變爲 resolved 時,執行 onFulfilled 方法,再次對 generator 實例執行 next 方法,而後重複整個過程。若是出現錯誤,則執行這個 promise 實例定義的 reject 函數即 onRejected 方法。
以上即實現了將異步過程同步化。
最後歡迎 star