開頭 首先本文有將近3000字,閱讀可能會(huì)占用你20分鐘左右。 文筆可能不佳,希望能幫助到閱讀此文的人有一些收獲
在進(jìn)行源碼閱讀前
首先抱有一個(gè)疑問,thunk函數(shù)是什么,thunkify庫又是干什么的,co又是干嘛,它有啥用
在 JavaScript 語言中,Thunk 函數(shù)替換的不是表達(dá)式,而是多參數(shù)函數(shù),將其替換成單參數(shù)的版本,且只接受回調(diào)函數(shù)作為參數(shù)
這幾句話來自阮一峰老師的blog文章試想下我們在node環(huán)境下要使用fs.readfile
fs.readfile("filename",function(err,data){ if(err){ console.log(err) return } })
而使用thunk簡單改造之后我們的函數(shù)可以變成這樣子的形式
var Thunk = function(filename){ return function (callback){ return fs.readfile(fileName,callback) } }
此時(shí)調(diào)用readfile的話,我們可以這么調(diào)用
var read = Thunk("filename") read(callback);
thunkify出自tj大神之手
thunkify源碼解析var assert = require("assert"); module.exports = thunkify; function thunkify(fn) { assert("function" == typeof fn, "function required"); // 引入斷言庫判斷是不是函數(shù) // 返回一個(gè)包含thunk函數(shù)的匿名函數(shù) return function () { var args = new Array(arguments.length); // 創(chuàng)建一個(gè)數(shù)組空間 var ctx = this; // 獲取上下文環(huán)境用于后面綁定上下文 for (var i = 0; i < args.length; ++i) { args[i] = arguments[i]; } // 迭代傳參,因?yàn)橛袃?nèi)存泄漏bug // 返回真正的thunk函數(shù) return function (done) { // done相當(dāng)于是執(zhí)行后的callback var called; // 聲明一個(gè)called保證只執(zhí)行一次這個(gè)回調(diào)函數(shù) // 壓入一個(gè)數(shù)組中進(jìn)行這種隔斷,防止被多次執(zhí)行 args.push(function () { if (called) return; called = true; done.apply(null, arguments); }); // 用try catch 在執(zhí)行失敗也走一次callback 傳入err信息 try { fn.apply(ctx, args); } catch (err) { done(err); } } } };
代碼并不難懂
乍一看,這好像沒什么用吧。
但 js后來有一個(gè)Generator函數(shù),thunk此時(shí)仿佛有了作用
Generator函數(shù)使用yield 就是將控制權(quán)放出暫停執(zhí)行
然后返回一個(gè)當(dāng)前指針(遍歷器對象)
所以我們是否需要有一種方法接受并且可以繼續(xù)返回這種控制權(quán)
顯式的調(diào)用next固然沒有問題。但是我們要自動(dòng)的話?該怎么辦
基于自動(dòng)流程管理,我們利用thunk函數(shù)的特性,調(diào)用回調(diào)函數(shù)callback
回調(diào)函數(shù)里面遞歸調(diào)用generator的next方法
直到狀態(tài)值為done generator函數(shù)結(jié)束
這時(shí)候整個(gè)generator就可以很優(yōu)雅地被解決
然后我們想象,這個(gè)流程thunk函數(shù)可以干什么
主要的功能其實(shí)是通過封裝多層使得我們可以在回調(diào)函數(shù)內(nèi)獲得控制權(quán)
返回控制權(quán)
因?yàn)橐话惆凑照懛?br>我們需要顯式地調(diào)用next next來使得我們的Generator一步步完成
那么我們只需要一種機(jī)制,可以幫助我們獲得控制權(quán),并且返回控制權(quán)
都可以實(shí)現(xiàn)自動(dòng)化
var fs = require("fs"); var thunkify = require("thunkify"); var readFile = thunkify(fs.readFile); var gen = function* (){ var r1 = yield readFile("xxxfilename"); console.log(r1.toString()); var r2 = yield readFile(" xxxfilename "); console.log(r2.toString()); }; function run(fn) { var gen = fn(); function next(err, data) { var result = gen.next(data); if (result.done) return; result.value(next); } next(); } run(gen);
這是一個(gè)簡單的demo利用thunkify實(shí)現(xiàn)自動(dòng)化generator
thunk函數(shù)回調(diào)調(diào)用next是一種方法
Pormise的then調(diào)用next 同時(shí)也是一種解決辦法
區(qū)別在于thunk可控(指的是在回調(diào)中我們可以可控執(zhí)行),promise立即執(zhí)行
Generator based control flow goodness for nodejs and the browser, using promises, letting you write non-blocking code in a nice-ish way.
基于Generator,使用promise,讓你用一種更好的方式書寫異步代碼
co的源碼也并不多
大概兩百行
https://github.com/tj/co/blob...
要讀懂co源碼建議還得看看promise規(guī)范與用法
var slice = Array.prototype.slice; module.exports = co["default"] = co.co = co; co.wrap = function (fn) { //兼容有參數(shù)的generator函數(shù) //利用柯里化將generator轉(zhuǎn)換成普通函數(shù) createPromise.__generatorFunction__ = fn; return createPromise; function createPromise() { return co.call(this, fn.apply(this, arguments)); } }; function co(gen) { var ctx = this; //獲得當(dāng)前上下文環(huán)境 var args = slice.call(arguments, 1); //獲得多參數(shù)(如果有的話) // we wrap everything in a promise to avoid promise chaining, // which leads to memory leak errors. // see https://github.com/tj/co/issues/180 //會(huì)內(nèi)存泄漏 //返回一個(gè)promise相當(dāng)于將一切都包裹在promise里面。使得我們co返回的可以使用promise的方法 // co的返回值是Promise對象。為什么可以then和catch的根源 return new Promise(function(resolve, reject) { //做類型的判斷。 if (typeof gen === "function") gen = gen.apply(ctx, args); //Generator函數(shù)執(zhí)行之后會(huì)是typeof會(huì)是對象。 //默認(rèn)執(zhí)行調(diào)用一次Generator返回一個(gè)遍歷器對象Generator if (!gen || typeof gen.next !== "function") return resolve(gen); //判斷類型 如果不符合 promise就進(jìn)入resolved // 看看是不是Generator指針 //傳入的不是Generators函數(shù),沒有next, // 就直接resolve返回結(jié)果;這里是錯(cuò)誤兼容而已,因?yàn)閏o就是基于generator function的,傳入其他的沒有意義 //執(zhí)行onFulfilled onFulfilled(); //返回一個(gè)promise //onFulfilled干了什么。其實(shí)跟我們之前的一樣,只是這里涉及到了promise的狀態(tài)。如果出錯(cuò)了。狀態(tài)返回是reject function onFulfilled(res) { var ret; try { ret = gen.next(res); //初始化啟動(dòng)一遍Generator next } catch (e) { return reject(e); //一有錯(cuò)誤的話就拋出錯(cuò)誤轉(zhuǎn)向rejected } // 初始化即將第一次yield的·值·傳給next next(ret); //將這個(gè)指針對象轉(zhuǎn)交next函數(shù)處理 // 實(shí)現(xiàn)自動(dòng)化的關(guān)鍵 return null; } function onRejected(err) { //接受error錯(cuò)誤 var ret; //這塊其實(shí)就是處理整個(gè)流程的錯(cuò)誤控制 try { ret = gen.throw(err); //利用Generator throw錯(cuò)誤給try catch捕獲 } catch (e) { return reject(e); //使得Promise進(jìn)入rejected } next(ret); } function next(ret) { //接受指針對象 if (ret.done) return resolve(ret.value); //顯示對ret指針狀態(tài)做判斷,done為true證明generator已經(jīng)結(jié)束 //此時(shí)進(jìn)入resolved結(jié)束整個(gè)Generator var value = toPromise.call(ctx, ret.value); //將yield 的值進(jìn)行Promise轉(zhuǎn)換 if (value && isPromise(value)) return value.then(onFulfilled, onRejected); //value在我們允許的范圍內(nèi),那么value.then注入onFulfilled與onRejected,來執(zhí)行下一次gen.next。 //在onFulfilled又將調(diào)用next從而使得next不停的利用then做調(diào)用 //如果值是存在并且可以進(jìn)行promise的轉(zhuǎn)換。(也就是不是基本類型/或假值) 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)過值轉(zhuǎn)換或者value為空的時(shí)候。此時(shí)將拋出錯(cuò)誤。 //因?yàn)槟蔷褪撬^的基本類型不支持了 //function, promise, generator, array, or object只支持這幾種的 } }); } //注意我們就只允許這幾種類型轉(zhuǎn)換。 //那么進(jìn)入判斷的時(shí)候我們就可以很簡單地判斷了,然后決定promise的狀態(tài) function toPromise(obj) { if (!obj) return obj; //如果obj undefined 或者別的假值返回這個(gè)undefined if (isPromise(obj)) return obj; //如果是個(gè)Promise的話就返回這個(gè)值 if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); //判斷是不是Generator function是的話用co處理 if ("function" == typeof obj) return thunkToPromise.call(this, obj); //如果是函數(shù)的話,使用thunk to promise轉(zhuǎn)換 if (Array.isArray(obj)) return arrayToPromise.call(this, obj); //如果是數(shù)組 使用array to promise if (isObject(obj)) return objectToPromise.call(this, obj); //如果是對象 使用object to promise 轉(zhuǎn)換 return obj; //如果都不是 就返回`值` } // co關(guān)于yield后邊的值也是有一定的要求的,只能是一個(gè) Function|Promise|Generator|Generator Function | Array | Object; // 而 yield Array和Object中的item也必須是 Function|Promise|Generator | Array | Object; // 如果不符合的話就將Promise rejected掉并發(fā)出警告 //下面是一些工具函數(shù) //使用thunk后的fnction 我們只允許它有一個(gè)參數(shù)callbak //允許有多個(gè)參數(shù) 第一個(gè)參數(shù)為error //在node環(huán)境下 第一個(gè)為error對象 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); }); }); } // thunkToPromise傳入一個(gè)thunk函數(shù) // 函數(shù)返回一個(gè)Promise對象 // promise里面執(zhí)行這個(gè)函數(shù) // nodejs的回調(diào)函數(shù) 第一個(gè)參數(shù)都是err // 如果有錯(cuò)誤就進(jìn)入rejected(前面我們可以看到 value.then(onFulfilled, onRejected); ) // 如果有error就rejected了 // 如果沒有的話就調(diào)用resolve( 后面onFulfilled ) //將數(shù)組中的所有值均promise化后執(zhí)行,Promise.all會(huì)等待數(shù)組內(nèi)所有promise均fulfilled、或者有一個(gè)rejected,才會(huì)執(zhí)行其后的then。 //對一些基本類型 例如數(shù)字 字符串之類的,是不會(huì)被toPromise轉(zhuǎn)換的 //最后在resolve(res)的時(shí)候 res就是存有所有異步操作執(zhí)行完的值數(shù)組 function arrayToPromise(obj) { return Promise.all(obj.map(toPromise, this)); } //對象通過key進(jìn)行遍歷, //對于每個(gè)被promise化好的value //都將其存儲(chǔ)于promises中,最后Promise.all, //生成results。 //objectToPromise實(shí)現(xiàn)實(shí)在是太可怕了=-= //所以很多字企圖把它講順了 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]); // 確保obj[key]為promise對象 // 然后調(diào)用defer推入 promises等待value的promise resolved之后將key放入results // 否則直接將 results[key] = obj[key](也就是無須promise化的) if (promise && isPromise(promise)) defer(promise, key); else results[key] = obj[key]; } // 利用promise.all來使用異步并行調(diào)用我們的promises // 如果執(zhí)行后進(jìn)入resolved然后壓入results對象 // 最后當(dāng)然是返回這個(gè)results對象 // 然后后面的then在獲得時(shí)候 onFulfilled onRejected的參數(shù)將是這個(gè)results // 這樣子我們每個(gè)promise的結(jié)果都會(huì)存在result對象對應(yīng)的key內(nèi) // 返回的是一個(gè)promise 后面也就可以接著.then(onFulfilled) 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[key] = res; })); } } //檢查是不是promise //·鴨子類型·判斷。 function isPromise(obj) { return "function" == typeof obj.then; } //判斷是不是Generator迭代器 function isGenerator(obj) { return "function" == typeof obj.next && "function" == typeof obj.throw; } //判斷是不是generator函數(shù) function isGeneratorFunction(obj) { var constructor = obj.constructor; if (!constructor) return false; if ("GeneratorFunction" === constructor.name || "GeneratorFunction" === constructor.displayName) return true; return isGenerator(constructor.prototype); } //判斷是不是對象 //plain object是指用JSON形式定義的普通對象或者new Object()創(chuàng)建的簡單對象 function isObject(val) { return Object == val.constructor; }
co大概就是干的,將generator自動(dòng)化,更好的將異步流轉(zhuǎn)換同步寫法
ES7的async await
其實(shí)就是generator的語法糖 再加上一個(gè)內(nèi)置自動(dòng)執(zhí)行的混合體
也就是究極體
await的返回值是一個(gè)promise
參考內(nèi)容:
阮一峰網(wǎng)絡(luò)日志
co 源碼分析 co 與 co.wrap
co 源碼分析
有兩種方法可以使Generator自動(dòng)化,thunk與Promise
Generator自動(dòng)化可以使得我們的異步代碼編寫得更像同步代碼(回調(diào)地獄是在太可怕了)
涉及異步的,如今很多都是利用Promise,所以掌握Promise是很重要的
希望閱讀此文的人可以有一些收獲。如果有什么錯(cuò)誤的地方也希望可以談出來或者私信我,一起探討。
渴望成長。^v^
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/84480.html
摘要:而這對于回調(diào)函數(shù)只有一個(gè)返回值參數(shù)的函數(shù),或者回調(diào)函數(shù)的第一個(gè)參數(shù)不表示錯(cuò)誤的函數(shù)來說,庫就無法使用了。當(dāng)對函數(shù)進(jìn)行時(shí),如果回調(diào)函數(shù)第一個(gè)參數(shù)是類型的對象才會(huì)被當(dāng)做錯(cuò)誤處理。因此,第二個(gè)回合,仍然是完勝和。 ES6 中引入了 Generator,Generator 通過封裝之后,可以作為協(xié)程來進(jìn)行使用。 其中對 Generator 封裝最為著名的當(dāng)屬 tj/co,但是 tj/co 跟 ...
摘要:沿用上面的例子,把包裝成一個(gè)對象這個(gè)回調(diào)就是等價(jià)于通過在里執(zhí)行回調(diào)函數(shù),獲取到上一步操作的結(jié)果和交回執(zhí)行權(quán),并把值傳遞回函數(shù)內(nèi)部,實(shí)現(xiàn)了遞歸執(zhí)行進(jìn)一步封裝,可以得到以下的代碼遞歸執(zhí)行 以前看過的內(nèi)容,感覺忘得差不多,最近抽空又看了一次,果然書讀百遍其義自見 Generator的執(zhí)行 Generator函數(shù)可以實(shí)現(xiàn)函數(shù)內(nèi)外的數(shù)據(jù)交換和執(zhí)行權(quán)交換。 從第一次調(diào)用next開始,從函數(shù)頭部開始...
摘要:寫在前面本文將要實(shí)現(xiàn)一個(gè)順序讀取文件的最優(yōu)方法,實(shí)現(xiàn)方式從最古老的回調(diào)方式到目前的,也會(huì)與大家分享下本人對于庫與庫的理解。其實(shí)的任何異步編程的解決方案的目標(biāo)都是要達(dá)到同步的語義,異步的執(zhí)行。 寫在前面 本文將要實(shí)現(xiàn)一個(gè)順序讀取文件的最優(yōu)方法,實(shí)現(xiàn)方式從最古老的回調(diào)方式到目前的async,也會(huì)與大家分享下本人對于thunk庫與co庫的理解。實(shí)現(xiàn)的效果:順序讀取出a.txt與b.txt,將...
摘要:從形式上將函數(shù)的執(zhí)行部分和回調(diào)部分分開,這樣我們就可以在一個(gè)地方執(zhí)行執(zhí)行函數(shù),在另一個(gè)地方執(zhí)行回調(diào)函數(shù)。這樣做的價(jià)值就在于,在做異步操作的時(shí)候,我們只需要知道回調(diào)函數(shù)執(zhí)行的順序和嵌套關(guān)系,就能按順序取得執(zhí)行函數(shù)的結(jié)果。 thunk thunk 從形式上將函數(shù)的執(zhí)行部分和回調(diào)部分分開,這樣我們就可以在一個(gè)地方執(zhí)行執(zhí)行函數(shù),在另一個(gè)地方執(zhí)行回調(diào)函數(shù)。這樣做的價(jià)值就在于,在做異步操作的時(shí)候,...
閱讀 800·2023-04-26 00:30
閱讀 2710·2021-11-23 09:51
閱讀 1056·2021-11-02 14:38
閱讀 2596·2021-09-07 10:23
閱讀 2254·2021-08-21 14:09
閱讀 1395·2019-08-30 10:57
閱讀 1611·2019-08-29 11:20
閱讀 1160·2019-08-26 13:53