本系列是關於Koa框架的文章,目前關注版本是Koa v1。主要分爲如下幾個方面:git
大名鼎鼎的co是什麼?它是TJ大神基於ES6的一些新特性開發的異步流程控制庫,基於它所開發的koa被視爲將來主流的web框架。segmentfault
koa基於co實現,而co又是使用了ES6的generator和promise特性。若是還不理解,能夠查看阮一峯老師的《ECMAScript 6 入門 --- Generator》和《ECMAScript 6 入門 --- Promise》。目前co升級爲4.X版本事,代碼進行了一次很有規模的重構,咱們主要關注co(4.X)的實現思路和源碼分析。數組
co(function* (){ var a = yield Promise.resolve('one'); console.log(a); var b = yield Promise.reslove('two'); console.log(b); return 'three'; }).then((value) => console.log(value)); // one // two // three
co(function* (){ var res = yield [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]; return res; }).then((value) => console.log(res)); // [1, 2, 3]
根據co的功能,它做爲異步流程控制的做用,自動調用generator對象的next()方法,實現generator函數的運行,並返回最終運行的結果。promise
若是要涉及到co的實現細節,咱們就會存在如下幾個疑問:app
如何依次調用next()方法框架
如何將yield後邊運算異步結果返回給對應的變量koa
co自身如何返回generator函數最後的return值
接下來咱們正對以上問題,分析TJ大神的源碼
function co(gen) { // 保持當前函數的上下文 var ctx = this; // 截取co輸入的參數,剔除arguments中的第一個參數,即gen對象,剩餘參數做爲gen的入參 var args = slice.call(arguments, 1); // 返回一個Promise對象,即最外圍Promise對象 return new Promise(function(resolve, reject) { // 判斷傳入的gen是否爲函數,如果則執行,將結果賦值給gen對象 // 若不是,則不執行 if (typeof gen === 'function') gen = gen.apply(ctx, args); // 根據generator函數執行結果是否存在next字段,判斷gen是否爲generator迭代器對象 // 若不是,則調用resolve返回最外圍Promise對象的狀態 if (!gen || typeof gen.next !== 'function') return resolve(gen); // 如果generator迭代器對象,開始控制gen.next()方法的調用 onFulfilled(); // 兩個用途 // 1、generator函數的執行入口 // 2、當作全部內部Promise對象的resolve方法,處理異步結果,並繼續調用下一個Promise function onFulfilled(res) { var ret; try { // gen運行至yield處被掛起,開始處理異步操做,並將異步操做的結果返回給ret.value ret = gen.next(res); } catch (e) { // 若報錯,直接調用reject返回外圍Promise對象的狀態,並傳出錯誤對象 return reject(e); } // 將gen.next的執行結果傳入next函數,實現依次串行調用gen.next方法 next(ret); return null; } // 當作全部內部Promise對象的reject方法,處理異步結果,並繼續調用下一個Promise function onRejected(err) { var ret; try { ret = gen.throw(err); } catch (e) { // 若報錯,直接調用reject返回外圍Promise對象的狀態,並傳出錯誤對象 return reject(e); } // 將gen.throw的執行結果傳入next函數,實現依次串行調用gen.next方法 next(ret); } // 實現串行調用gen.next的核心 function next(ret) { // 判斷內部Promise是否所有執行完畢 // 若執行完畢,直接調用resolve改變外圍Promise的狀態,並返回最終的return值[問題3] if (ret.done) return resolve(ret.value); // 若未執行完畢,調用toPromise方法將上一個Promise返回的值轉化爲Promise對象 // 具體參見toPromise方法 var value = toPromise.call(ctx, ret.value); // 根據value轉化後的Promise對象的兩個狀態,執行下一個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函數中yield的過程總結以下:
進入外圍Promise
經過入口onFilfilled()方法,將generator函數運行至第一個yield處,執行該yield後邊的異步操做,並將結果傳入next方法
若是next中傳入結果的done爲true,則返回外圍Promise的resolve
若是next中傳入結果的done爲true,則返回value(即yield後邊的對象)是否能夠轉化爲內部Promise對象。如沒法轉化,則拋出錯誤,返回外圍Promise的reject
若能轉化爲Promise對象,將全部內部Promise並行執行,經過then(onFilfilled, onRejected)開始執行
在onFilfilled()或者onRejected()內部調用再次調用next()方法,實現串行執行yield,並肩yield後邊的對象傳遞給next(),依次重複。
全部yield執行返回,將最後的return值返回給外圍Promise的resovle方法,結束co對generator函數的調用
可以在co中實現generator函數的逐步調用next()方法,轉化爲內部Promise將相當重要,而源碼是如何轉化的呢?哪些對象又是可以轉化的呢?接下來,咱們看下源碼。
function toPromise(obj) { // 確保obj有意義 if (!obj) return obj; // 如果Promise對象,則直接返回 if (isPromise(obj)) return obj; // 如果generator函數或者generator對象,則傳入一個新的co,並返回新co的外圍Promise // 做爲當前co的內部Promise,這樣實現多層級調用 if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); // 如果函數,則返回thunk規範的函數 if ('function' == typeof obj) return thunkToPromise.call(this, obj); // 如果數組,把數組中每一個元素轉化爲內部Promise,返回Promise.all並行運算 if (Array.isArray(obj)) return arrayToPromise.call(this, obj); // 如果對象,遍歷對象中的每一個key對應的value,轉化成Promise.all並行運算 if (isObject(obj)) return objectToPromise.call(this, obj); return obj; } 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); }); }); } function arrayToPromise(obj) { // Array.map並行計算返回每個元素的Promise return Promise.all(obj.map(toPromise, this)); } 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]; } // Promise鏈式調用,後續的then能偶獲取此處的results return Promise.all(promises).then(function () { return results; }); function defer(promise, key) { // key對應的元素成功轉化爲Promise對象後,構造這些Promise的resovle方法 // 以便在results中獲取每一個Promise對象成功執行後結果 results[key] = undefined; promises.push(promise.then(function (res) { results[key] = res; })); } }
結合上述分析,咱們能夠獲得,yield後面只能是函數、Promise對象、Generator函數、Generator迭代器對象、數組(元素僅限以前的4類)和Object(對應value僅限定以前的4類)