秒杀 tj/co 的 hprose 协程库

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.coyield 的支撑则跟 await 完整兼容,支撑对一切范例的数据举行 yield

hprose.co 对 chunk 函数举行 yield 时,假如回调函数第一个参数是 Error 范例的对象才会被当作毛病处置惩罚。假如回调函数只要一个参数且不是 Error 范例的对象,则作为返回值看待。假如回调函数有两个以上的参数,假如第一个参数为 nullundefined,则第一个参数被当作无毛病被疏忽,不然,悉数回调参数都被当作返回值看待。假如被当作返回值的回调参数有多个,则这多个参数被当作数组结果看待,假如只要一个,则该参数被直接当作返回值看待。

下面我们来举例说明一下:

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

我们可以看出,awaithprose.co 除了对生成器的处置惩罚差别之外,别的的都雷同。关于生成器函数,await 是按原样返回的,而 hprose.co 则是根据 tj/co 的体式格局处置惩罚。也就是说 hprose.co 综合了 awaittj/co 的悉数长处。运用 hprose.co 比运用 awaittj/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/coasync/await

yield thunk 函数

我们再来看看 tj/cotj/thunkify 是何等的让人抓狂,以及 hprose.cohprose.thunkify 是怎样文雅的处理 tj/cotj/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.cohprose.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);
});

然后替代差别的 cothunkify,然后实行,我们会发明,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.cohprose.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);
});

这里开首对 hprosetj 版本的差别 cothunkify 完成的援用就省略了,请人人自行脑补。

上面这段顺序,假如运用 tj 版本的 cothunkify 完成,运转结果是如许的:

(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 版本的 cothunkify 完成,运转结果是如许的:

3
5
3

hprose 版本的运转结果再次相符预期,而 tj 版本的运转结果再次让人扫兴之极。

进过上面三个回合的较劲,我们发明 hprose 的协程完胜 tjasync/await,而且 tj 的完成是惨败,async/await 虽然比 tj 轻微好那末一点,然则跟 hprose 所完成协程比起来,也是瞠乎其后。

所以,用 tj/coasync/await 觉得很不爽的同砚,可以尝尝 hprose.co 了,相对让你爽歪歪。

hprose 有 4 个 JavaScript 版本,它们都支撑上面的协程库,它们的地点分别是:

别的,假如你不须要运用 hprose 序列化和长途挪用的话,下面另有一个特地的从 hprose 中精简出来的 Promise A+ 完成和协程库:Future.js(oschina镜像)

固然该协程库的功用不止于此,更多引见请拜见:

    原文作者:andot
    原文地址: https://segmentfault.com/a/1190000007573266
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞