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