ES6 中引入了 Generator,Generator 经过封装以后,能够做为协程来进行使用。javascript
其中对 Generator 封装最为著名的当属 tj/co,可是 tj/co 跟 ES2016 的 async/await 相比的话,还存在一些比较严重的缺陷。html
hprose 中也引入了对 Generator 封装的协程支持,可是比 tj/co 更加完善,下面咱们就来详细介绍一下它们之间的差异。html5
tj/co
有如下几个方面的问题:java
首先,tj/co
库中的 yield
只支持 thunk 函数,生成器函数,promise 对象,以及数组和对象,可是不支持普通的基本类型的数据,好比 null
, 数字,字符串等都不支持。这对于 yield
一个类型不肯定的变量来讲,是很不方便的。并且这跟 await
也是不兼容的。node
其次,在 yield
数组和对象时,tj/co
库会自动对数组中的元素和对象中的字段递归的遍历,将其中的全部的 Promise
元素和字段替换为实际值,这对于简单的数据来讲,会方便一些。可是对于带有循环引用的数组和对象来讲,会致使没法获取到结果,这是一个致命的问题。即便对于不带有循环引用结构的数组和对象来讲,若是该数组和对象比较复杂,这也会消耗大量的时间。并且这跟 await
也是不兼容的。git
再次,对于 thunk 函数,tj/co
库会认为回调函数第一个参数必须是表示错误,从第二个参数开始才表示返回值。而这对于回调函数只有一个返回值参数的函数,或者回调函数的第一个参数不表示错误的函数来讲,tj/co
库就没法使用了。github
而 hprose.co
对 yield
的支持则跟 await
彻底兼容,支持对全部类型的数据进行 yield
。编程
当 hprose.co
对 chunk 函数进行 yield
时,若是回调函数第一个参数是 Error
类型的对象才会被当作错误处理。若是回调函数只有一个参数且不是 Error
类型的对象,则做为返回值对待。若是回调函数有两个以上的参数,若是第一个参数为 null
或 undefined
,则第一个参数被当作无错误被忽略,不然,所有回调参数都被当作返回值对待。若是被当作返回值的回调参数有多个,则这多个参数被当作数组结果对待,若是只有一个,则该参数被直接当作返回值对待。bootstrap
下面咱们来举例说明一下:小程序
首先咱们来看一下 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
都方便。
咱们来看第二个让 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
。
咱们再来看看 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 序列化和远程调用的话,下面还有一个专门的从 hprose 中精简出来的 Promise A+ 实现和协程库:Future.js(oschina镜像)
固然该协程库的功能不止于此,更多介绍请参见: