摘要:其實就是判斷了的屬性是不是個函數再接著,如果是個函數或者生成器,那就像你自己調用函數一樣,手動傳到里面去執行。
前言
原文地址
源碼地址
如何使用了解co的前提是已經知曉generator是什么,可以看軟大神的Generator 函數的語法,
co是TJ大神寫的能夠使generator自動執行的函數庫,而我們熟知的koa也用到了它管理異步流程控制,將異步任務書寫同步化,爽的飛起,也擺脫了一直以來的回調地獄問題。
首先我們根據co的官方文檔來稍做改變看下,到底如何使用co,再一步步進行源碼分析工作(這篇文章分析的co版本是4.6.0)。
yield 后面常見的可以跟的類型
promises
array (parallel execution)
objects (parallel execution)
generator functions (delegation)
promises
let co = require("co") let genTimeoutFun = (delay) => { return () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`delayTime: ${delay}`) }, delay) }) } } let timeout1 = genTimeoutFun(1000) let timeout2 = genTimeoutFun(200) co(function * () { let a = yield timeout1() console.log(a) // delayTime: 1000 let b = yield timeout2() console.log(b) // delayTime: 200 return "end" }).then((res) => { console.log(res) })
array
let co = require("co") let genTimeoutFun = (delay) => { return () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`delayTime: ${delay}`) }, delay) }) } } let timeout1 = genTimeoutFun(1000) let timeout2 = genTimeoutFun(200) co(function * () { let a = yield [timeout1(), timeout2()] console.log(a) // [ "delayTime: 1000", "delayTime: 200" ] return "end" }).then((res) => { console.log(res) // end })
objects
let co = require("co") let genTimeoutFun = (delay) => { return () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`delayTime: ${delay}`) }, delay) }) } } let timeout1 = genTimeoutFun(1000) let timeout2 = genTimeoutFun(200) co(function * () { let a = yield { timeout1: timeout1(), timeout2: timeout2() } console.log(a) // { timeout1: "delayTime: 1000",timeout2: "delayTime: 200" } return "end" }).then((res) => { console.log(res) // end })
generator functions
let co = require("co") let genTimeoutFun = (delay) => { return () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`delayTime: ${delay}`) }, delay) }) } } let timeout1 = genTimeoutFun(1000) let timeout2 = genTimeoutFun(200) function * gen () { let a = yield timeout1() console.log(a) // delayTime: 1000 let b = yield timeout2() console.log(b) // delayTime: 200 } co(function * () { yield gen() return "end" }).then((res) => { console.log(res) // end })
最后說一下,關于執行傳入的generator函數接收參數的問題
let co = require("co") co(function * (name) { console.log(name) // qianlongo }, "qianlongo")
從co函數的第二個參數開始,便是傳入的generator函數可以接收的實參
開始分析源碼你可以把以上代碼拷貝至本地測試一番看看效果,接下來我們一步步開始分析co的源碼
首先經過上面的例子可以發現,co函數本身接收一個generator函數,并且co執行后返回的是Promise
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) { if (typeof gen === "function") gen = gen.apply(ctx, args); if (!gen || typeof gen.next !== "function") return resolve(gen); // xxx }); }
在Promise的內部,先執行了外部傳入的gen,執行的結果如果不具備next屬性(且要是一個函數),就直接返回,并將執行成功回調resolve(gen),否則得到的是一個指針對象。
接下來繼續看...
onFulfilled(); /** * @param {Mixed} res * @return {Promise} * @api private */ function onFulfilled(res) { var ret; try { ret = gen.next(res); // 用上面執行gen之后的generator生成器將指針指向下一個位置 } catch (e) { return reject(e); } next(ret); // 緊接著執行next,正是它實現了反復調用自己,自動流程控制,注意ret(即上一次gen.next執行后返回的對象{value: xxx, done: true or false}) } /** * @param {Error} err * @return {Promise} * @api private */ function onRejected(err) { var ret; try { ret = gen.throw(err); } catch (e) { return reject(e); } next(ret); }
我覺得可以把 onFulfilled 和 onRejected 看成是返回的Promise的resolve和reject。
而onFulfilled也是將原生的generator生成器的next方法包裝了一遍,大概是為了抓取錯誤吧(看到內部的try catch了嗎)
好,我們看到了co內部將指針移動到了第一個位置之后,接著執行了內部的next方法,接下來聚焦在該函數上
function next(ret) { // 如果整個generator函數的內部狀態已經表示走完,便將Promise的狀態設置為成功狀態,并執行resolve if (ret.done) return resolve(ret.value); // 這一步是將ret的value轉換為Promise形式 var value = toPromise.call(ctx, ret.value); // 這里非常關鍵,是co實現自己調用自己,實現流程自動化的關鍵 // 注意這里使用value.then,即為返回值添加成功和失敗的回調,在成功的回調里面再去執行onFulfilled,緊接著就是調用內部的next函數 // 那不是就保證了流程完全按照你寫的順序來了? if (value && isPromise(value)) return value.then(onFulfilled, onRejected); // 拋出錯誤,yield后只能跟著指定的下列這幾種類型 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是怎么將異步流程自動管理起來了
但是我對next函數中的toPromise函數還有疑問,他到底做了什么事?使得co(generatorFun)中yield可以支持數組、對象、generator函數等形式。
一步步來看
function toPromise(obj) { // obj不存在,直接返回 if (!obj) return obj; // 如果obj已經是Promise,則也是直接返回 if (isPromise(obj)) return obj; // 如果是個generator函數或者generator生成器,那就像你自己調用co函數一樣,手動傳到co里面去執行 if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); // 如果obj既不是Promise,也不是isGeneratorFunction和isGenerator,要是一個普通的函數(需要符合thunk函數規范),就將該函數包裝成Promise的形式 if ("function" == typeof obj) return thunkToPromise.call(this, obj); // 如果是一個數組的形式,就去arrayToPromise包裝一番 if (Array.isArray(obj)) return arrayToPromise.call(this, obj); if (isObject(obj)) return objectToPromise.call(this, obj); return obj; }
首先如果obj不存在,就直接返回,你想啊,co本來就是依賴上一次指針返回的value是Promise或者其他,這個時候如果返回
{ value: false, done: false }
那就沒有必要再給一個false值轉成Promise形式了吧。
接著,如果obj本身就是個Promise也是直接返回,用了內部的isPromise函數進行判斷,我們看下他怎么實現的。
function isPromise(obj) { return "function" == typeof obj.then; }
其實就是判斷了obj的then屬性是不是個函數
再接著,如果是個generator函數或者generator生成器,那就像你自己調用co函數一樣,手動傳到co里面去執行。
isGeneratorFunction
function isGeneratorFunction(obj) { var constructor = obj.constructor; if (!constructor) return false; if ("GeneratorFunction" === constructor.name || "GeneratorFunction" === constructor.displayName) return true; return isGenerator(constructor.prototype); }
通過obj的constructor屬性去判斷其是否屬于GeneratorFunction,最后如果constructor屬性沒判斷出來,再去用isGenerator,判斷obj的原型是不是generator生成器
function isGenerator(obj) { return "function" == typeof obj.next && "function" == typeof obj.throw; }
判斷的條件也比較直接,需要符合兩個條件,一個是obj.next要是一個函數,一個是obj.throw要是一個函數
接下來繼續看
如果obj既不是Promise,也不是isGeneratorFunction和isGenerator,要是一個普通的函數,就將該函數包裝成Promise的形式,這里我們主要需要看thunkToPromise
function thunkToPromise(fn) { var ctx = this; // 將thunk函數包裝成Promise return new Promise(function (resolve, reject) { // 執行這個thunk函數 fn.call(ctx, function (err, res) { // 注意thunk函數內部接收的回調函數中傳入的第一個參數是err,出現了err,當然需要走reject了 if (err) return reject(err); // 參數是兩個以上的情況下,將參數整成一個數組 if (arguments.length > 2) res = slice.call(arguments, 1); // 最后執行成功的回調 resolve(res); }); }); }
接下來是重頭戲了,co中如果處理yield后面跟一個數組呢?主要是arrayToPromise函數的作用
function arrayToPromise(obj) { // 使用到了Promise.all,將obj中多個promise實例(當然你也可以在數組中填thunk函數,generator函數等)重新包裝成一個。最后返回一個新的Promise return Promise.all(obj.map(toPromise, this)); }
還有最后一個判斷,如果obj是個對象怎么辦?
function objectToPromise(obj){ // 構造一個和傳入對象有相同構造器的對象, results也是 var results = new obj.constructor(); // 獲取obj的keys var keys = Object.keys(obj); // 存儲obj中是Promise的屬性 var promises = []; for (var i = 0; i < keys.length; i++) { var key = keys[i]; var promise = toPromise.call(this, obj[key]); // 如果是結果是Promise,則用defer函數對results進行修改 if (promise && isPromise(promise)) defer(promise, key); // 如果是非Promise就按原樣返回 else results[key] = obj[key]; } // 最后 使用到了Promise.all,將obj中多個promise實例 return Promise.all(promises).then(function () { return results; }); function defer(promise, key) { // predefine the key in the result results[key] = undefined; promises.push(promise.then(function (res) { // 運行成功之后再講結果賦值給results results[key] = res; })); } }結尾
到這里,co源碼分析就告一段落了??偢杏X有些沒有說到位,歡迎大家拍磚,晚安。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/83049.html
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...
摘要:拿到下一個中間件后把他交給去處理當中間件執行結束了,就把的狀態設置為成功。 前言 原文地址 最近幾天花了比較長的時間在koa(1)的源碼分析上面,初次看的時候,被中間件執行那段整的暈乎乎的,完全不知道所以,再次看,好像明白了些什么,再反復看,我去,簡直神了,簡直淚流滿面,簡直喪心病狂?。。。?showImg(https://segmentfault.com/img/remote/146...
閱讀 3435·2021-11-12 10:36
閱讀 2757·2021-11-11 16:55
閱讀 2975·2021-09-27 13:36
閱讀 1627·2021-08-05 10:01
閱讀 3566·2019-08-30 15:55
閱讀 782·2019-08-30 13:01
閱讀 1918·2019-08-29 17:16
閱讀 2388·2019-08-29 16:40