摘要:而這對于回調函數只有一個返回值參數的函數,或者回調函數的第一個參數不表示錯誤的函數來說,庫就無法使用了。當對函數進行時,如果回調函數第一個參數是類型的對象才會被當做錯誤處理。因此,第二個回合,仍然是完勝和。
ES6 中引入了 Generator,Generator 通過封裝之后,可以作為協程來進行使用。
其中對 Generator 封裝最為著名的當屬 tj/co,但是 tj/co 跟 ES2016 的 async/await 相比的話,還存在一些比較嚴重的缺陷。
hprose 中也引入了對 Generator 封裝的協程支持,但是比 tj/co 更加完善,下面我們就來詳細介紹一下它們之間的差別。
tj/co 有以下幾個方面的問題:
首先,tj/co 庫中的 yield 只支持 thunk 函數,生成器函數,promise 對象,以及數組和對象,但是不支持普通的基本類型的數據,比如 null, 數字,字符串等都不支持。這對于 yield 一個類型不確定的變量來說,是很不方便的。而且這跟 await 也是不兼容的。
其次,在 yield 數組和對象時,tj/co 庫會自動對數組中的元素和對象中的字段遞歸的遍歷,將其中的所有的 Promise 元素和字段替換為實際值,這對于簡單的數據來說,會方便一些。但是對于帶有循環引用的數組和對象來說,會導致無法獲取到結果,這是一個致命的問題。即使對于不帶有循環引用結構的數組和對象來說,如果該數組和對象比較復雜,這也會消耗大量的時間。而且這跟 await 也是不兼容的。
再次,對于 thunk 函數,tj/co 庫會認為回調函數第一個參數必須是表示錯誤,從第二個參數開始才表示返回值。而這對于回調函數只有一個返回值參數的函數,或者回調函數的第一個參數不表示錯誤的函數來說,tj/co 庫就無法使用了。
而 hprose.co 對 yield 的支持則跟 await 完全兼容,支持對所有類型的數據進行 yield。
當 hprose.co 對 chunk 函數進行 yield 時,如果回調函數第一個參數是 Error 類型的對象才會被當做錯誤處理。如果回調函數只有一個參數且不是 Error 類型的對象,則作為返回值對待。如果回調函數有兩個以上的參數,如果第一個參數為 null 或 undefined,則第一個參數被當做無錯誤被忽略,否則,全部回調參數都被當做返回值對待。如果被當做返回值的回調參數有多個,則這多個參數被當做數組結果對待,如果只有一個,則該參數被直接當做返回值對待。
下面我們來舉例說明一下:
yield 基本類型首先我們來看一下 tj/co 庫的例子:
var co = require("co"); co(function*() { try { console.log(yield Promise.resolve("promise")); console.log(yield function *() { return "generator" }); console.log(yield new Date()); console.log(yield 123); console.log(yield 3.14); console.log(yield "hello"); console.log(yield true); } catch (e) { console.error(e); } });
該程序運行結果為:
promise generator TypeError: You may only yield a function, promise, generator, array, or object, but the following object was passed: "Sat Nov 19 2016 14:51:09 GMT+0800 (CST)" at next (/usr/local/lib/node_modules/co/index.js:101:25) at onFulfilled (/usr/local/lib/node_modules/co/index.js:69:7) at process._tickCallback (internal/process/next_tick.js:103:7) at Module.runMain (module.js:577:11) at run (bootstrap_node.js:352:7) at startup (bootstrap_node.js:144:9) at bootstrap_node.js:467:3
其實除了前兩個,后面的幾個基本類型的數據都不能被 yield。如果我們把上面代碼的第一句改為:
var co = require("hprose").co;
后面的代碼都不需要修改,我們來看看運行結果:
promise generator 2016-11-19T06:54:30.081Z 123 3.14 hello true
也就是說,hprose.co 支持對所有類型進行 yield 操作。下面我們再來看看 async/await 是什么效果:
(async function() { try { console.log(await Promise.resolve("promise")); console.log(await function *() { return "generator" }); console.log(await new Date()); console.log(await 123); console.log(await 3.14); console.log(await "hello"); console.log(await true); } catch (e) { console.error(e); } })();
上面的代碼基本上就是把 co(function*...) 替換成了 async function...,把 yield 替換成了 await。
我們來運行上面的程序,注意,對于當前版本的 node 運行時需要加上 --harmony_async_await 參數,運行結果如下:
promise [Function] 2016-11-19T08:16:25.316Z 123 3.14 hello true
我們可以看出,await 和 hprose.co 除了對生成器的處理不同以外,其它的都相同。對于生成器函數,await 是按原樣返回的,而 hprose.co 則是按照 tj/co 的方式處理。也就是說 hprose.co 綜合了 await 和 tj/co 的全部優點。使用 hprose.co 比使用 await 或 tj/co 都方便。
yield 數組或對象我們來看第二個讓 tj/co 崩潰的例子:
var co = require("co"); co(function*() { try { var a = []; for (i = 0; i < 1000000; i++) { a[i] = i; } var start = Date.now();; yield a; var end = Date.now();; console.log(end - start); } catch (e) { console.error(e); } }); co(function*() { try { var a = []; a[0] = a; console.log(yield a); } catch (e) { console.error(e); } }); co(function*() { try { var o = {}; o.self = o; console.log(yield o); } catch (e) { console.error(e); } });
運行該程序,我們會看到程序會卡一會兒,然后出現下面的結果:
2530 (node:70754) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): RangeError: Maximum call stack size exceeded (node:70754) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. (node:70754) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): RangeError: Maximum call stack size exceeded
上面的 2530 是第一個 co 程序段輸出的結果,也就是說這個 yield 要等待 2.5 秒才能返回結果。而后面兩個 co 程序段則直接調用棧溢出了。如果在實際應用中,出現了這樣的數據,使用 tj/co 你的程序就會變得很慢,或者直接崩潰了。
下面看看 hprose.co 的效果,同樣只替換第一句話為:
var co = require("hprose").co;
后面的代碼都不需要修改,我們來看看運行結果:
7 [ [Circular] ] { self: [Circular] }
第一個 co 程序段用時很短,只需要 7 ms。注意,這還是包含了后面兩個程序段的時間,因為這三個協程是并發的,如果去掉后面兩個程序段,你看的輸出可能是 1 ms 或者 0 ms。而后面兩個程序段也完美的返回了帶有循環引用的數據。這才是我們期望的結果。
我們再來看看 async/await 下是什么效果,程序代碼如下:
(async function() { try { var a = []; for (i = 0; i < 1000000; i++) { a[i] = i; } var start = Date.now(); await a; var end = Date.now(); console.log(end - start); } catch (e) { console.error(e); } })(); (async function() { try { var a = []; a[0] = a; console.log(await a); } catch (e) { console.error(e); } })(); (async function() { try { var o = {}; o.self = o; console.log(await o); } catch (e) { console.error(e); } })();
運行結果如下:
14 [ [Circular] ] { self: [Circular] }
我們發現 async/await 的輸出結果跟 hprose.co 是一致的,但是在性能上,hprose.co 則比 async/await 還要快 1 倍。因此,第二個回合,hprose.co 仍然是完勝 tj/co 和 async/await。
yield thunk 函數我們再來看看 tj/co 和 tj/thunkify 是多么的讓人抓狂,以及 hprose.co 和 hprose.thunkify 是如何優雅的解決 tj/co 和 tj/thunkify 帶來的這些讓人抓狂的問題的。
首先我們來看第一個問題:
tj/thunkify 返回的 thunk 函數的執行結果是一次性的,不能像 promise 結果那樣被使用多次,我們來看看下面這個例子:
var co = require("co"); var thunkify = require("thunkify"); var sum = thunkify(function(a, b, callback) { callback(null, a + b); }); co(function*() { var result = sum(1, 2); console.log(yield result); console.log(yield sum(2, 3)); console.log(yield result); });
這個例子很簡單,輸出結果你猜是啥?
3 5 3
是上面的結果嗎?恭喜你,答錯了!不過,這不是你的錯,而是 tj/thunkify 的錯,它的結果是:
3 5
什么?最后的 console.log(yield result) 輸出結果哪兒去了?不好意思,tj/thunkify 解釋說是為了防止 callback 被重復執行,所以就只能這么玩了。可是真的是這樣嗎?
我們來看看使用 hprose.co 和 hprose.thunkify 的執行結果吧,把開頭兩行換成下面三行:
var hprose = require("hprose"); var co = hprose.co; var thunkify = hprose.thunkify;
其它代碼都不用改,運行它,你會發現預期的結果出來了,就是:
3 5 3
可能你還不服氣,你會說,tj/thunkify 這樣做是為了防止類似被 thunkify 的函數中,回調被多次調用時,yield 的結果不正確,比如:
var sum = thunkify(function(a, b, callback) { callback(null, a + b); callback(null, a + b + a); }); co(function*() { var result = sum(1, 2); console.log(yield result); console.log(yield sum(2, 3)); console.log(yield result); });
如果 tj/thunkify 不這樣做,結果可能就會變成:
3 4 5
可是真的是這樣嗎?你會發現,即使改成上面的樣子,hprose.thunkify 配合 hprose.co 返回的結果仍然是:
3 5 3
跟預期的一樣,回調函數并沒有重復執行,錯誤的結果并沒有出現。而且當需要重復 yield 結果函數時,還能夠正確得到結果。
最后我們再來看一下,tj/thunkify 這樣做真的解決了問題了嗎?我們把代碼改成下面這樣:
var sum = thunkify(function(a, b, callback) { console.log("call sum(" + Array.prototype.join.call(arguments) + ")"); callback(null, a + b); callback(null, a + b + a); }); co(function*() { var result = sum(1, 2); console.log(yield result); console.log(yield sum(2, 3)); console.log(yield result); });
然后替換不同的 co 和 thunkify,然后執行,我們會發現,tj 版本的輸出如下:
call sum(1,2,function (){ if (called) return; called = true; done.apply(null, arguments); }) 3 call sum(2,3,function (){ if (called) return; called = true; done.apply(null, arguments); }) 5 call sum(1,2,function (){ if (called) return; called = true; done.apply(null, arguments); },function (){ if (called) return; called = true; done.apply(null, arguments); })
而 hprose 版本的輸出結果如下:
call sum(1,2,function () { thisArg = this; results.resolve(arguments); }) 3 call sum(2,3,function () { thisArg = this; results.resolve(arguments); }) 5 3
從這里,我們可以看出,tj 版本的程序在執行第二次 yield result 時,簡直錯的離譜,它不但沒有讓我們得到預期的結果,反而還重復執行了 thunkify 后的函數,而且帶入的參數也完全不對了,所以,這是一個完全錯誤的實現。
而從 hprose 版本的輸出來看,hprose 不但完美的避免了回調被重復執行,而且保證了被 thunkify 后的函數執行的結果被多次 yield 時,也不會被重復執行,而且還能夠得到預期的結果,可以實現跟返回 promise 對象一樣的效果。
tj 因為沒有解決他所實現的 thunkify 函數帶來的這些問題,所以在后期推薦大家放棄 thunkify,轉而投奔到返回 promise 對象的懷抱中,而實際上,這個問題并非是不能解決的。
hprose 在對 thunkify 函數的處理上,再次完勝 tj。而這個回合中,async/await 就不用提了,因為 async/await 完全不支持對 thunk 函數進行 await。
這還不是 hprose.co 和 hprose.thunkify 的全部呢,再繼續看下面這個例子:
var sum = thunkify(function(a, b, callback) { callback(a + b); }); co(function*() { var result = sum(1, 2); console.log(yield result); console.log(yield sum(2, 3)); console.log(yield result); });
這里開頭對 hprose 和 tj 版本的不同 co 和 thunkify 實現的引用就省略了,請大家自行腦補。
上面這段程序,如果使用 tj 版本的 co 和 thunkify 實現,運行結果是這樣的:
(node:75927) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): 3 (node:75927) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
而如果使用 hprose 版本的 co 和 thunkify 實現,運行結果是這樣的:
3 5 3
hprose 版本的運行結果再次符合預期,而 tj 版本的運行結果再次讓人失望之極。
進過上面三個回合的較量,我們發現 hprose 的協程完勝 tj 和 async/await,而且 tj 的實現是慘敗,async/await 雖然比 tj 稍微好那么一點,但是跟 hprose 所實現協程比起來,也是望塵莫及。
所以,用 tj/co 和 async/await 感覺很不爽的同學,可以試試 hprose.co 了,絕對讓你爽歪歪。
hprose 有 4 個 JavaScript 版本,它們都支持上面的協程庫,它們的地址分別是:
hprose for Node.js(oschina鏡像)
hprose for HTML5(oschina鏡像)
hprose for JavaScript(oschina鏡像)
hprose for 微信小程序(oschina鏡像)
另外,如果你不需要使用 hprose 序列化和遠程調用的話,下面還有一個專門的從 hprose 中精簡出來的 Promise A+ 實現和協程庫:Future.js(oschina鏡像)
當然該協程庫的功能不止于此,更多介紹請參見:
Promise 異步編程
協程
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/80996.html
摘要:只要在調用異步函數時設置一個或多個回調函數,函數就會在完成時自動調用回調函數。要解決的問題是,如何將回調方法的參數從回調方法中傳遞出來,讓它可以像同步函數的返回結果一樣,在回調函數以外的控制范圍內,可以傳遞和復用。 摘要: 我們知道 JavaScript 自從有了 Generator 之后,就有了各種基于 Generator 封裝的協程。其中 hprose 中封裝的 Promise 和...
摘要:上一篇文章第一章異步及協程基礎第二節關鍵字下一篇文章第二章實戰演練開發網站第一節網站結構使用協程可以開發出類似同步代碼的異步行為。協程函數可以通過以下三張方式調用在本身是協程的函數內通過關鍵字調用。 上一篇文章:Python:Tornado 第一章:異步及協程基礎:第二節:Python關鍵字yield下一篇文章:Python:Tornado 第二章:實戰演練:開發Tornado網站:第...
摘要:年開發并發布框架現已停止維護。經過一年實戰,年月日,一周年之際正式發布版本。宇潤部分開源項目我已通過碼云平臺,向項目力所能及地捐款,聊表心意。所以,目前主打的還是單體應用開發。協議的開發,也是帶來的一大優勢。 imi 介紹 showImg(https://segmentfault.com/img/bVbuab9?w=291&h=187); imi 是基于 PHP 協程應用開發框架,它支...
摘要:異步編程程序執行分為同步和異步,如果程序每執行一步都需要等待上一步完成才能開始,此所謂同步。因此異步編程十分重要。 異步編程 程序執行分為同步和異步,如果程序每執行一步都需要等待上一步完成才能開始,此所謂同步。如果程序在執行一段代碼的同時可以去執行另一段代碼,等到這段代碼執行完畢再吧結果交給另一段代碼,此所謂異步。比如我們需要請求一個網絡資源,由于網速比較慢,同步編程就意味著用戶必須等...
閱讀 3441·2021-11-22 09:34
閱讀 1909·2019-08-30 12:53
閱讀 3503·2019-08-28 18:07
閱讀 2990·2019-08-27 10:55
閱讀 2967·2019-08-26 10:12
閱讀 3597·2019-08-23 18:21
閱讀 1350·2019-08-23 14:10
閱讀 1486·2019-08-23 13:04