開頭 首先本文有將近3000字,閱讀可能會占用你20分鐘左右。 文筆可能不佳,希望能幫助到閱讀此文的人有一些收獲
在進行源碼閱讀前
首先抱有一個疑問,thunk函數是什么,thunkify庫又是干什么的,co又是干嘛,它有啥用
在 JavaScript 語言中,Thunk 函數替換的不是表達式,而是多參數函數,將其替換成單參數的版本,且只接受回調函數作為參數
這幾句話來自阮一峰老師的blog文章試想下我們在node環境下要使用fs.readfile
fs.readfile("filename",function(err,data){ if(err){ console.log(err) return } })
而使用thunk簡單改造之后我們的函數可以變成這樣子的形式
var Thunk = function(filename){ return function (callback){ return fs.readfile(fileName,callback) } }
此時調用readfile的話,我們可以這么調用
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"); // 引入斷言庫判斷是不是函數 // 返回一個包含thunk函數的匿名函數 return function () { var args = new Array(arguments.length); // 創建一個數組空間 var ctx = this; // 獲取上下文環境用于后面綁定上下文 for (var i = 0; i < args.length; ++i) { args[i] = arguments[i]; } // 迭代傳參,因為有內存泄漏bug // 返回真正的thunk函數 return function (done) { // done相當于是執行后的callback var called; // 聲明一個called保證只執行一次這個回調函數 // 壓入一個數組中進行這種隔斷,防止被多次執行 args.push(function () { if (called) return; called = true; done.apply(null, arguments); }); // 用try catch 在執行失敗也走一次callback 傳入err信息 try { fn.apply(ctx, args); } catch (err) { done(err); } } } };
代碼并不難懂
乍一看,這好像沒什么用吧。
但 js后來有一個Generator函數,thunk此時仿佛有了作用
Generator函數使用yield 就是將控制權放出暫停執行
然后返回一個當前指針(遍歷器對象)
所以我們是否需要有一種方法接受并且可以繼續返回這種控制權
顯式的調用next固然沒有問題。但是我們要自動的話?該怎么辦
基于自動流程管理,我們利用thunk函數的特性,調用回調函數callback
回調函數里面遞歸調用generator的next方法
直到狀態值為done generator函數結束
這時候整個generator就可以很優雅地被解決
然后我們想象,這個流程thunk函數可以干什么
主要的功能其實是通過封裝多層使得我們可以在回調函數內獲得控制權
返回控制權
因為一般按照正常寫法
我們需要顯式地調用next next來使得我們的Generator一步步完成
那么我們只需要一種機制,可以幫助我們獲得控制權,并且返回控制權
都可以實現自動化
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);
這是一個簡單的demo利用thunkify實現自動化generator
thunk函數回調調用next是一種方法
Pormise的then調用next 同時也是一種解決辦法
區別在于thunk可控(指的是在回調中我們可以可控執行),promise立即執行
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規范與用法
var slice = Array.prototype.slice; module.exports = co["default"] = co.co = co; co.wrap = function (fn) { //兼容有參數的generator函數 //利用柯里化將generator轉換成普通函數 createPromise.__generatorFunction__ = fn; return createPromise; function createPromise() { return co.call(this, fn.apply(this, arguments)); } }; 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 //會內存泄漏 //返回一個promise相當于將一切都包裹在promise里面。使得我們co返回的可以使用promise的方法 // co的返回值是Promise對象。為什么可以then和catch的根源 return new Promise(function(resolve, reject) { //做類型的判斷。 if (typeof gen === "function") gen = gen.apply(ctx, args); //Generator函數執行之后會是typeof會是對象。 //默認執行調用一次Generator返回一個遍歷器對象Generator if (!gen || typeof gen.next !== "function") return resolve(gen); //判斷類型 如果不符合 promise就進入resolved // 看看是不是Generator指針 //傳入的不是Generators函數,沒有next, // 就直接resolve返回結果;這里是錯誤兼容而已,因為co就是基于generator function的,傳入其他的沒有意義 //執行onFulfilled onFulfilled(); //返回一個promise //onFulfilled干了什么。其實跟我們之前的一樣,只是這里涉及到了promise的狀態。如果出錯了。狀態返回是reject function onFulfilled(res) { var ret; try { ret = gen.next(res); //初始化啟動一遍Generator next } catch (e) { return reject(e); //一有錯誤的話就拋出錯誤轉向rejected } // 初始化即將第一次yield的·值·傳給next next(ret); //將這個指針對象轉交next函數處理 // 實現自動化的關鍵 return null; } function onRejected(err) { //接受error錯誤 var ret; //這塊其實就是處理整個流程的錯誤控制 try { ret = gen.throw(err); //利用Generator throw錯誤給try catch捕獲 } catch (e) { return reject(e); //使得Promise進入rejected } next(ret); } function next(ret) { //接受指針對象 if (ret.done) return resolve(ret.value); //顯示對ret指針狀態做判斷,done為true證明generator已經結束 //此時進入resolved結束整個Generator var value = toPromise.call(ctx, ret.value); //將yield 的值進行Promise轉換 if (value && isPromise(value)) return value.then(onFulfilled, onRejected); //value在我們允許的范圍內,那么value.then注入onFulfilled與onRejected,來執行下一次gen.next。 //在onFulfilled又將調用next從而使得next不停的利用then做調用 //如果值是存在并且可以進行promise的轉換。(也就是不是基本類型/或假值) return onRejected(new TypeError("You may only yield a function, promise, generator, array, or object, " + "but the following object was passed: "" + String(ret.value) + """)); //如果沒有經過值轉換或者value為空的時候。此時將拋出錯誤。 //因為那就是所謂的基本類型不支持了 //function, promise, generator, array, or object只支持這幾種的 } }); } //注意我們就只允許這幾種類型轉換。 //那么進入判斷的時候我們就可以很簡單地判斷了,然后決定promise的狀態 function toPromise(obj) { if (!obj) return obj; //如果obj undefined 或者別的假值返回這個undefined if (isPromise(obj)) return obj; //如果是個Promise的話就返回這個值 if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); //判斷是不是Generator function是的話用co處理 if ("function" == typeof obj) return thunkToPromise.call(this, obj); //如果是函數的話,使用thunk to promise轉換 if (Array.isArray(obj)) return arrayToPromise.call(this, obj); //如果是數組 使用array to promise if (isObject(obj)) return objectToPromise.call(this, obj); //如果是對象 使用object to promise 轉換 return obj; //如果都不是 就返回`值` } // co關于yield后邊的值也是有一定的要求的,只能是一個 Function|Promise|Generator|Generator Function | Array | Object; // 而 yield Array和Object中的item也必須是 Function|Promise|Generator | Array | Object; // 如果不符合的話就將Promise rejected掉并發出警告 //下面是一些工具函數 //使用thunk后的fnction 我們只允許它有一個參數callbak //允許有多個參數 第一個參數為error //在node環境下 第一個為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傳入一個thunk函數 // 函數返回一個Promise對象 // promise里面執行這個函數 // nodejs的回調函數 第一個參數都是err // 如果有錯誤就進入rejected(前面我們可以看到 value.then(onFulfilled, onRejected); ) // 如果有error就rejected了 // 如果沒有的話就調用resolve( 后面onFulfilled ) //將數組中的所有值均promise化后執行,Promise.all會等待數組內所有promise均fulfilled、或者有一個rejected,才會執行其后的then。 //對一些基本類型 例如數字 字符串之類的,是不會被toPromise轉換的 //最后在resolve(res)的時候 res就是存有所有異步操作執行完的值數組 function arrayToPromise(obj) { return Promise.all(obj.map(toPromise, this)); } //對象通過key進行遍歷, //對于每個被promise化好的value //都將其存儲于promises中,最后Promise.all, //生成results。 //objectToPromise實現實在是太可怕了=-= //所以很多字企圖把它講順了 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對象 // 然后調用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來使用異步并行調用我們的promises // 如果執行后進入resolved然后壓入results對象 // 最后當然是返回這個results對象 // 然后后面的then在獲得時候 onFulfilled onRejected的參數將是這個results // 這樣子我們每個promise的結果都會存在result對象對應的key內 // 返回的是一個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函數 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()創建的簡單對象 function isObject(val) { return Object == val.constructor; }
co大概就是干的,將generator自動化,更好的將異步流轉換同步寫法
ES7的async await
其實就是generator的語法糖 再加上一個內置自動執行的混合體
也就是究極體
await的返回值是一個promise
參考內容:
阮一峰網絡日志
co 源碼分析 co 與 co.wrap
co 源碼分析
有兩種方法可以使Generator自動化,thunk與Promise
Generator自動化可以使得我們的異步代碼編寫得更像同步代碼(回調地獄是在太可怕了)
涉及異步的,如今很多都是利用Promise,所以掌握Promise是很重要的
希望閱讀此文的人可以有一些收獲。如果有什么錯誤的地方也希望可以談出來或者私信我,一起探討。
渴望成長。^v^
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84480.html
摘要:而這對于回調函數只有一個返回值參數的函數,或者回調函數的第一個參數不表示錯誤的函數來說,庫就無法使用了。當對函數進行時,如果回調函數第一個參數是類型的對象才會被當做錯誤處理。因此,第二個回合,仍然是完勝和。 ES6 中引入了 Generator,Generator 通過封裝之后,可以作為協程來進行使用。 其中對 Generator 封裝最為著名的當屬 tj/co,但是 tj/co 跟 ...
摘要:沿用上面的例子,把包裝成一個對象這個回調就是等價于通過在里執行回調函數,獲取到上一步操作的結果和交回執行權,并把值傳遞回函數內部,實現了遞歸執行進一步封裝,可以得到以下的代碼遞歸執行 以前看過的內容,感覺忘得差不多,最近抽空又看了一次,果然書讀百遍其義自見 Generator的執行 Generator函數可以實現函數內外的數據交換和執行權交換。 從第一次調用next開始,從函數頭部開始...
摘要:從形式上將函數的執行部分和回調部分分開,這樣我們就可以在一個地方執行執行函數,在另一個地方執行回調函數。這樣做的價值就在于,在做異步操作的時候,我們只需要知道回調函數執行的順序和嵌套關系,就能按順序取得執行函數的結果。 thunk thunk 從形式上將函數的執行部分和回調部分分開,這樣我們就可以在一個地方執行執行函數,在另一個地方執行回調函數。這樣做的價值就在于,在做異步操作的時候,...
閱讀 785·2023-04-26 00:30
閱讀 2689·2021-11-23 09:51
閱讀 1045·2021-11-02 14:38
閱讀 2560·2021-09-07 10:23
閱讀 2243·2021-08-21 14:09
閱讀 1363·2019-08-30 10:57
閱讀 1603·2019-08-29 11:20
閱讀 1149·2019-08-26 13:53