Skip to content
小马哥 edited this page Nov 19, 2016 · 28 revisions

基本用法

ES6 中引入了 GeneratorGenerator 通过封装之后,可以作为协程来进行使用。

其中对 Generator 封装最为著名的当属 tj/co,但是 tj/co 跟 ES2016 的 async/await 相比的话,还存在一些比较严重的缺陷。

hprose 中也引入了对 Generator 封装的协程支持,而且比 tj/co 更加完善,后面我们会详细介绍它们之间的一些差别。

下面先让我们来看一个例子:

var hprose = require('hprose');

hprose.co(function*() {
    var client = hprose.Client.create('http://hprose.com/example/');
    yield client.useService();
    console.log(yield client.hello("World"));
});

hprose.co(也可以是 hprose.Future.co)就是一个协程封装函数。它的功能是以协程的方式来执行生成器函数。该方法允许带入参数执行。

在上面的例子中,client 是一个 Hprose 的 HTTP 客户端。Hprose 的 JavaScript 版本(包括 NodeJS, HTML5, JavaScript微信小程序专用版)的客户端为异步客户端,所以它上面的调用都是异步调用。

因为 JavaScript 的 Proxy(中文版) 具有浏览器兼容性问题,所以在客户端代理对象的生成上,使用的是动态获取服务列表的方式来实现的。

上面的 yield client.useService() 语句就是用于返回这个客户端代理对象,使用默认参数调用时,会将 client 对象本身设置为代理对象,因此这里我们没有使用返回值,如果要在其它地方使用的话,最好是保存它的返回值。

client.useService() 的返回值是一个 Promise 对象,如果不使用协程,那么在使用时,需要使用 then 方法,然后在其回调中才能使用,而使用协程,直接使用 yield 就可以获得实际的代理对象了。而且因为在这里 client 本身就是代理对象,因此当 yield 返回代理对象之后,client 对象就已经被初始化好了,因此后面就可以直接调用 client 上的 hello 方法了。

clienthello 方法的返回值也是个 Promise 对象,使用 yield 之后,它的返回值就变成了实际值,也就可以直接用 console.log 进行打印了。

通过上面的例子,我们可以看出,使用协程方式,Hprose 调用就被完全同步化了。这可以大大简化异步程序的编写。

协程兼容性问题

因为 Generator 是在 ES6 中引入了,所以比较老版本的 NodeJS 是不支持的,而浏览器的支持就更少了,目前只有 Chrome 和 Firefox 支持,而 IE、Opera、Safari 都不支持,HyBird App 也不支持。

那是否意味着这个功能很鸡肋呢?并不是,因为现在有许多工具可以将 ES6 代码转换为 ES5 代码,比如 Babel。其中就包括对 Generator 的支持。所以,即使你使用了协程,仍然可以通过这些转换器转换为在各种浏览器中都可以运行的程序。

为了方便用户使用,在 hprose 中还直接集成了 regenerator-runtime.js,不需要额外引入这个文件了。

微信小程序因为缺少全局对象,仍然需要使用:

const regeneratorRuntime = require("regenerator-runtime.js");

的方式来单独引入该文件,不过该文件也已经放在微信小程序专用版中了,免去了用户单独寻找该文件的麻烦。

与 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 数组或对象

yield thunk 函数

因此,hprose.co 完全解决了 tj/co 的三个主要缺陷。用 tj/co 感觉很不爽的同学,可以试试 hprose.co,绝对让你爽歪歪。

Clone this wiki locally