Skip to content

Hprose 客户端

小马哥 edited this page Aug 8, 2015 · 65 revisions

概述

Hprose 2.0 for Node.js 支持多种底层网络协议绑定的客户端,比如:HTTP 客户端,Socket 客户端和 WebSocket 客户端。

其中 HTTP 客户端支持跟 HTTP、HTTPS 绑定的 hprose 服务器通讯。

Socket 客户端支持跟 TCP、UNIX Socket 绑定的 hprose 服务器通讯,并且支持全双工和半双工两种模式。

WebSocket 客户端支持跟 ws、wss 绑定的 hprose 服务器通讯。

尽管支持这么多不同的底层网络协议,但除了在对涉及到底层网络协议的参数设置上有所不同以外,其它的用法都完全相同。因此,我们在下面介绍 hprose 客户端的功能时,若未涉及到底层网络协议的区别,就以 HTTP 客户端为例来进行说明。

创建客户端

创建客户端有两种方式,一种是直接使用构造器函数,另一种是使用工厂方法 create

使用构造器函数创建客户端

hprose.Client 是一个抽象类,因此它不能作为构造器函数直接使用。如果你想创建一个具体的底层网络协议绑定的客户端,你可以将它作为父类,至于如何实现一个具体的底层网络协议绑定的客户端,这已经超出了本手册的内容范围,这里不做具体介绍,有兴趣的读者可以参考 hprose.HttpClienthprose.SocketClienthprose.WebSocketClient 这几个底层网络协议绑定客户端的实现源码。

hprose.HttpClienthprose.SocketClienthprose.WebSocketClient 这三个函数是可以直接使用的构造器函数。它们分别对应 http 客户端、Socket 客户端和 WebSocket 客户端。

new hprose.HttpClient([uri[, functions[, settings]]]);
new hprose.SocketClient([uri[, functions[, settings]]]);
new hprose.WebSocketClient([uri[, functions[, settings]]]);

这三个构造器的参数格式是相同的。开头的关键字 new 也可以省略,但最好不要省略。

构造器中包含了 3 个参数,这 3 个参数都可以省略。

当 3 个参数都省略时,创建的客户端是未初始化的,后面需要使用 useService 方法进行初始化,这是后话,暂且不表。

第 1 个参数 uri 是服务器地址,该服务器地址可以是单个的 uri 字符串,也可以是由多个 uri 字符串组成的数组。当该参数为多个 uri 字符串组成的数组时,客户端会从这些地址当中随机选择一个作为服务地址。因此需要保证这些地址发布的都是完全相同的服务。

第 2 个参数 functions 是远程函数名集合。它可以是单个函数名的字符串表示,也可以是多个函数名的字符串数组,还可以是一个对象。

第 3 个参数 settings 用于初始化客户端的设置。它可以初始化客户端的以下设置:

  • failswitch
  • timeout
  • retry
  • idempotent
  • keepAlive
  • byref
  • simple
  • useHarmonyMap
  • filter
  • options

这些设置都有对应的客户端属性,这里暂不解释,在后面介绍属性时,再分别介绍。

例如:

var client = new hprose.HttpClient(uri, 'hello', { timeout: 20000 });

创建的 client 对象上,就有一个叫 hello 的远程方法,并且客户端的超时被初始化为 20000ms。

这个 hello 方法可以直接这样调用:

var result = client.hello('world');

再举一例:

var client = new hprose.HttpClient(uri, ['hello', 'sum']);

这样创建的 client 对象上,就有两个远程方法,他们分别是 hellosum

var client = new hprose.HttpClient(uri, {
    user: ['add', 'update', 'del', 'get']
});

这样创建的 client 对象上,就有一个叫 user 的对象,在这个 user 对象上有 4 个方法,他们分别是 add, update, del, get

可以这样调用:

var result = client.user.get(id);

注意:这里的 user.adduser.updateuser.deluser.get 分别对应服务器端发布的别名为:user_add, user_update, user_deluser_get 方法。

服务器端的别名可以通过 _ 分隔成好几段,每一段都可以转换为 . 调用的方式。

另外,对象和数组方式还可以组合使用,例如下面这个复杂一点的例子:

var functions = {
    user: ['add', 'update', 'del', 'get'],
    order: [ 'add', 'update', 'del', 'get', {
        today: [ 'add', 'udate', 'del', 'get' ],
        yesterday: [ 'add', 'udate', 'del', 'get' ],
        tomorrow: [ 'add', 'udate', 'del', 'get' ]
    }]
};

var client = new hprose.HttpClient(uri, functions);

在上面的例子中:client.order.add 方法对应服务器端的 order_add 方法,而 client.order.today.add 方法则对应服务器端的 order_today_add 方法。

当然,如果方法有很多,像这样一个一个都列出来,或许有些麻烦。所以这个函数列表可以省略。

如果省略的话,想要直接使用方法名调用需要在 client 对象的 ready 方法回调中才能使用,例如:

var hprose = require('hprose');
var client = new hprose.HttpClient(uri);
client.ready(function(proxy) {
    proxy.hello('world', function(result) {
        console.log(result);
    });
});

因为当省略函数名列表时,客户端会向服务器端请求这个函数名列表,当获取到之后才会将这些函数绑定到客户端对象上。而获取的过程是异步的,因此需要使用 ready 方法。

在启动客户端时,如果使用 --harmony-proxies 参数启动的话,则客户端不需要去请求函数名列表,而是直接通过创建一个 Proxy 对象来实现方法名动态绑定的效果,但因为创建的对象有别于 client 对象本身,因此也需要使用 ready 方法才能获取到这个 proxy 对象。

在省略函数名列表的情况下,使用 --harmony-proxies 参数启动还有另外一个好处:不加 --harmony-proxies,对于 user_add 这样的方法是不能使用 user.add 的方式调用的。而如果使用了 --harmony-proxies,就可以使用 user.addorder.today.add 这种方式来调用了。

通过工厂方法 create 创建客户端

hprose.Client.create(uri[, functions[, settings]]);
hprose.HttpClient.create(uri[, functions[, settings]]);
hprose.SocketClient.create(uri[, functions[, settings]]);
hprose.WebSocketClient.create(uri[, functions[, settings]]);

与构造器函数不同,工厂方法 create 可以在 hprose.Client 上被调用,它会根据 uri 的协议来决定创建什么类型的客户端。

create 方法与构造器函数的参数一样,返回结果也一样。但是第一个参数 uri 不能被省略。

create 方法与构造器函数还有一点不同,create 会检查 uri 的有效性(是指格式是否有效,而不是指服务器是否可以连通),而构造器函数不会检查。

因此,除非在创建客户端的时候,不想指定服务地址,否则,应该优先考虑使用 create 方法来创建客户端。

使用 hprose.Client.create 方法还有个好处,当你变更底层通讯协议不需要修改代码,只需要修改 uri 地址就可以了,而 uri 地址可以通过各种方式动态加载,因此更加灵活。

uri 地址格式

HTTP 服务地址格式

HTTP 服务地址与普通的 URL 地址没有区别,支持 httphttps 两种协议,这里不做介绍。

WebSocket 服务地址格式

除了协议从 http 改为 ws(或 wss) 以外,其它部分与 http 地址表示方式完全相同,这里不再详述。

TCP 服务地址格式

<protocol>://<ip>:<port>

<ip> 是服务器的 IP 地址,也可以是域名。

<port> 是服务器的端口号,hprose 的 TCP 服务没有默认端口号,因此不可省略。

<protocol> 表示协议,它可以为以下取值:

  • tcp
  • tcp4
  • tcp6
  • tls
  • tcps
  • tcp4s
  • tcp6s

tcp 表示 tcp 协议,地址可以是 ipv6 地址,也可以是 ipv4 地址。

tcp4 表示地址为 ipv4 的 tcp 协议。

tcp6 表示地址为 ipv6 的 tcp 协议。

tlstcps 意义相同,表示安全的 tcp 协议,地址可以是 ipv6 地址,也可以是 ipv4 地址。如有必要,可设置客户端安全证书。

tcp4s 表示地址为 ipv4 的安全的 tcp 协议。

tcp6s 表示地址为 ipv6 的安全的 tcp 协议。

UNIX Socket 服务地址格式

unix:<path>

path 表示 socket 文件的路径。

例如 unix:/tmp/my.sock,这里注意,协议和路径之间没有 // 分隔。

客户端属性

uri 属性

只读属性。该属性表示客户端当前所使用的地址。如果客户端在创建时设置了多个服务地址,该属性的值仅为这多个地址中当前正在使用中的那个地址。

id 属性

只读属性。该属性表示当前客户端在进行推送订阅时的唯一编号。在没有进行推送订阅或者使用自己指定 id 方式进行推送订阅时,该属性的值为 null。你也可以调用 client['#'](); 方法来手动从服务器端获取该 id 的值。

failswitch 属性

该属性为 Boolean 类型。默认值为 false

该属性表示当前客户端在因网络原因调用失败时是否自动切换服务地址。当客户端服务地址仅设置一个时,不管该属性值为何,都不会切换地址。

你也可以针对某个调用进行单独设置。

timeout 属性

该属性为整数类型,默认值为 30000,单位是毫秒(ms)。

该属性表示当前客户端在调用时的超时时间,如果调用超过该时间后仍然没有返回,则会以超时错误返回。

你也可以针对某个调用进行单独设置。

idempotent 属性

该属性为 Boolean 类型,默认值为 false

该属性表示调用是否为幂等性调用,幂等性调用表示不论该调用被重复几次,对服务器的影响都是相同的。幂等性调用在因网络原因调用失败时,会自动重试。如果 failswitch 属性同时被设置为 true,并且客户端设置了多个服务地址,在重试时还会自动切换地址。

你也可以针对某个调用进行单独设置。

retry 属性

该属性为整数类型,默认值为 10

该属性表示幂等性调用在因网络原因调用失败后的重试次数。只有 idempotent 属性为 true 时,该属性才有作用。

你也可以针对某个调用进行单独设置。

byref 属性

该属性为 Boolean 类型,默认值为 false

该属性表示调用是否为引用参数传递。当设置为引用参数传递时,服务器端会传回修改后的参数值(即使没有修改也会传回)。因此,当不需要该功能时,设置为 false 会比较节省流量。

你也可以针对某个调用进行单独设置。

simple 属性

该属性为 Boolean 类型,默认值为 false

该属性表示调用中所传输的数据是否为简单数据。简单数据是指:null、数字(包括整数、长整数、浮点数)、Boolean 值、字符串、二进制数据、日期时间等基本类型的数据或者不包含引用的数组、Map 和对象。当该属性设置为 true 时,在进行序列化操作时,将忽略引用处理,加快序列化速度。但如果数据不是简单类型的情况下,将该属性设置为 true,可能会因为死循环导致堆栈溢出的错误。

简单的讲,用 JSON 可以表示的数据都是简单数据。但是对于比较复杂的 JSON 数据,设置 simpletrue 可能不会加快速度,反而会减慢,比如对象数组。因为默认情况下,hprose 会对对象数组中的重复字符串的键值进行引用处理,这种引用处理可以对序列化起到优化作用。而关闭引用处理,也就关闭了这种优化。

你也可以针对某个调用进行单独设置。

因为不同调用的数据可能差别很大,因此,建议不要修改默认设置,而是针对某个调用进行单独设置。

useHarmonyMap 属性

该属性为 Boolean 类型,默认值为 false

该属性表示调用所返回的数据中,如果包含有 Map 类型的数据,是否反序列化为 ECMAScript 6 中的 Map 类型对象。当该属性设置为 false 时(即默认值),Map 类型的数据将会被反序列化为 Object 实例对象的数据。

除非 Map 中的键不是字符串类型,否则没必要将该属性设置为 true

你也可以针对某个调用进行单独设置。

keepAlive 属性

该属性为 Boolean 类型,默认值为 true

该属性表示客户端和服务器端之间是否保持长连接。当服务器端支持长连接时,可有效改善客户端调用性能。通常,不需要对该属性值进行修改。除非你发现你的服务器不支持 keepAlive 特性(某些不太符合规范的 HTTP 服务器可能会有此问题),并且会有异常发生时,你才需要将该属性设置为 false

options 属性

该属性为对象类型,默认值为不包含任何属性的空对象。

该属性在不同的协议下,可以设置不同的选项,比如可以设置 httpstls 的客户端证书等选项。具体选项值可以参考 Node.js 网络部分的选项值。

filter 属性

该属性可以为对象类型或对象数组类型。默认值为 null。

该属性的作用是可以设置一个或多个 Filter 对象。关于 Filter 对象,我们将作为单独的章节进行介绍,这里暂且略过。

onerror 属性

该属性为函数类型,默认值为空函数(即无任何操作的函数)。

当客户端调用发生错误时,如果没有为调用设置异常处理回调函数,则该属性将被回调。回调函数有两个参数,第一个参数是方法名(字符串类型),第二个参数是调用中发生的错误(通常为 Error 对象)。

该属性还有一个 onError 的别名。

在 Node.js 中,也可以使用 client.on('error', function(name, error) { ... }); 的方式来设置错误回调。效果跟使用 onerror 属性相同。

客户端方法

setOption 方法

client.setOption(option, value);

该方法同设置 options 属性类似,区别在于使用该方法是设置单个选项值。

addFilter 方法

client.addFilter(filter);

该方法同设置 filter 属性类似。该方法用于添加一个 filter 对象到 Filter 链的末尾。

removeFilter 方法

client.removeFilter(filter);

该方法同设置 filter 属性类似。该方法用于从 Filter 链中删除指定的 filter 对象。

useService 方法

client.useService();
client.useService(uri);
client.useService(functions);
client.useService(uri, functions);

该方法的用处是对于未初始化的客户端对象,进行后期初始化设置,或者用于变更服务器地址。

当未设置任何参数调用时,如果 node 启动时没有使用 --harmony-proxies 参数,该客户端返回一个 promise 对象,该 promise 对象的成功值为远程服务代理对象。如果使用了 --harmony-proxies 参数,则返回一个远程服务代理对象。

当仅设置了 uri 参数时,跟上面的功能相同,但是会替换当前的 uri 设置。注意,这里的 uri 地址只能是单个的服务地址,而不能是服务地址数组列表。

functions 参数是一个服务方法列表,与创建客户端时的 functions 参数相同,但不能是单个的字符串方法名。当设置了该列表参数后,不论 node 启动时有没有使用 --harmony-proxies 参数,都会直接返回一个远程服务代理对象。

例如:

var hprose = require('hprose');
var client = new hprose.HttpClient('http://www.hprose.com/example/', ['hello']);
client.hello("World", function(result) { console.log(result); });

跟下面的代码的效果完全相同。

var hprose = require('hprose');
var client = new hprose.HttpClient();
var proxy = client.useService('http://www.hprose.com/example/', ['hello']);
proxy.hello("World", function(result) { console.log(result); });

注意,这里的 proxy 对象跟 client 实际上是同一个对象。如果希望有所区别,可以在 useService 方法的最后加上一个 true 的参数。该参数表示创建一个不同于 client 的新的 proxy 对象。

ready 方法

前面在介绍创建客户端的时候已经介绍过了。它的作用是,当远程方法被动态绑定到客户端上后,触发 ready 中的回调函数,执行对远程方法的调用。

invoke 方法

直接使用远程方法名调用

client[name]([arg1, arg2, ... argn[, onsuccess[, onerror[, settings]]]]);

name 是要调用的远程函数/方法名。

arg1...argn 是这个远程函数/方法的参数。如果这个方法没有参数,那就不需要写任何参数。有几个就写几个。参数不能是 function 类型,这一点不难理解,因为一个函数是不能作为参数传给远程服务器执行的。

onsuccess 是远程函数/方法调用成功时的回调函数。它是 function 类型。正是因为前面的参数不可能是 function 类型,因此这里只要遇到第一个是 function 类型的参数,那么就可以认为它是回调函数。

onerror 是远程函数/方法调用失败时的回调函数。它也是 function 类型。它跟在 onsuccess 之后,因此,如果你想传入一个处理失败情况的回调函数,那么 onsuccess 这个回调函数是不能省略的。

settings 是对该远程函数/方法的单独设置,这里面包括前面介绍属性时提到的那些可以单独设置的属性,还有几个是属性中不具有而特别针对调用时的设置。因为该参数是一个对象,因此,它之前的 onsuccess 参数也不能省略(但是 onerror 可以省略),否则它无法被识别为是远程方法的参数还是远程方法的设置。

使用 invoke 调用

client.invoke(name[, args[, onsuccess[, onerror[, settings]]]]);

该方法是客户端的最核心方法,它的功能就是进行远程调用,并返回一个表示结果的 promise 对象。

该方法与直接使用远程方法名调用功能类似。但有以下几点区别:

  1. 直接使用远程方法名调用时,参数 arg1...argn 中的参数可以是 promise 对象。直接使用 invoke 方法时,args 是参数数组,里面的元素不可以包含 promise 对象。
  2. 当同时使用远程方法名和 invoke 方法进行远程调用时,不管哪个写在前面,都是 invoke 方法先执行,原因是使用远程方法名调用时,会先对对参数中的 promise 对象进行取值操作,而 invoke 方法调用不会有这个过程。
  3. invoke 方法的 args 必须是数组类型,但可以省略,省略的话,被认为该调用没有参数。
  4. invoke 方法的 onsuccess 在省略的情况下,仍然可以带入 settings 参数。但直接使用远程方法名调用时,如果 onsuccess 省略,则不能带入 settings 参数。

settings 参数可以包括以下设置:

  • mode
  • byref
  • simple
  • failswitch
  • timeout
  • idempotent
  • retry
  • oneway
  • sync
  • onsuccess
  • onerror
  • useHarmonyMap
  • userdata

下面来分别介绍一下这些设置的意义:

mode

该设置表示结果返回的类型,它有4个取值,分别是:

  • hprose.Normal (或 hprose.ResultMode.Normal
  • hprose.Serialized (或 hprose.ResultMode.Serialized
  • hprose.Raw (或 hprose.ResultMode.Raw
  • hprose.RawWithEndTag (或 hprose.ResultMode.RawWithEndTag

hprose.Normal 是默认值,表示返回正常的已被反序列化的结果。

hprose.Serialized 表示返回的结果保持序列化的格式。

hprose.Raw 表示返回原始数据。

hprose.RawWithEndTag 表示返回带有结束标记的原始数据。

这样说明也许有些晦涩,让我们来看一个例子就清楚了:

var hprose = require('hprose');
var BytesIO = hprose.BytesIO;
var client = hprose.Client.create('http://www.hprose.com/example/', ['hello']);
function onsuccess(result) {
    console.log(result.constructor, BytesIO.toString(result));
}
client.hello("World", onsuccess, { mode: hprose.Normal, sync: true });
client.hello("World", onsuccess, { mode: hprose.Serialized, sync: true });
client.hello("World", onsuccess, { mode: hprose.Raw, sync: true });
client.hello("World", onsuccess, { mode: hprose.RawWithEndTag, sync: true });

为了保证执行顺序,这里还加了一个 sync 设置,对于该设置在后面再做详细解释。

该程序执行结果如下:

[Function: String] 'Hello World'
[Function: Uint8Array] 's11"Hello World"'
[Function: Uint8Array] 'Rs11"Hello World"'
[Function: Uint8Array] 'Rs11"Hello World"z'

由于历史原因,为了兼容旧版本的 hprose 的写法,该设置也可以不写在 settings 对象中,例如上面程序还可以这样写:

var hprose = require('hprose');
var BytesIO = hprose.BytesIO;
var client = hprose.Client.create('http://www.hprose.com/example/', ['hello']);
function onsuccess(result) {
    console.log(result.constructor, BytesIO.toString(result));
}
client.hello("World", onsuccess, hprose.Normal, { sync: true });
client.hello("World", onsuccess, hprose.Serialized, { sync: true });
client.hello("World", onsuccess, hprose.Raw, { sync: true });
client.hello("World", onsuccess, hprose.RawWithEndTag, { sync: true });

但在新版本中,不再推荐这种写法。

byref

该设置表示调用是否为引用参数传递方式。例如:

var hprose = require('hprose');
var client = hprose.Client.create('http://www.hprose.com/example/',
                                 ['swapKeyAndValue']);
var weeks = {
    'Monday': 'Mon',
    'Tuesday': 'Tue',
    'Wednesday': 'Wed',
    'Thursday': 'Thu',
    'Friday': 'Fri',
    'Saturday': 'Sat',
    'Sunday': 'Sun',
};
function onsuccess(result, args) {
    console.log(weeks.constructor, weeks);
    console.log(result.constructor, result);
    console.log(args.constructor, args);
}
client.swapKeyAndValue(weeks, onsuccess, { byref: true });

该程序执行结果为:

[Function: Object] { Monday: 'Mon',
  Tuesday: 'Tue',
  Wednesday: 'Wed',
  Thursday: 'Thu',
  Friday: 'Fri',
  Saturday: 'Sat',
  Sunday: 'Sun' }
[Function: Object] { Mon: 'Monday',
  Tue: 'Tuesday',
  Wed: 'Wednesday',
  Thu: 'Thursday',
  Fri: 'Friday',
  Sat: 'Saturday',
  Sun: 'Sunday' }
[Function: Array] [ { Mon: 'Monday',
    Tue: 'Tuesday',
    Wed: 'Wednesday',
    Thu: 'Thursday',
    Fri: 'Friday',
    Sat: 'Saturday',
    Sun: 'Sunday' } ]

我们可以看到在回调方法中的 args 参数被改变了,但是原来的参数对象 weeks 并没有被改变。也就是说,这里的引用参数传递只体现在回调函数返回的参数上,对原始的参数并不会修改。

同样,由于历史原因,为了兼容旧版本的 hprose 的写法,该设置也可以不写在 settings 对象中,而直接将 true 跟在 onsuccess 之后也是可以的。

simple

该设置表示本次调用中所传输的参数是否为简单数据。前面在属性介绍中已经进行了说明,这里就不在重复。

failswitch

该设置表示当前调用在因网络原因失败时是否自动切换服务地址。

timeout

该设置表示本次调用的超时时间,如果调用超过该时间后仍然没有返回,则会以超时错误返回。

idempotent

该设置表示本次调用是否为幂等性调用,幂等性调用在因网络原因调用失败时,会自动重试。

retry

该设置表示幂等性调用在因网络原因调用失败后的重试次数。只有 idempotent 设置为 true 时,该设置才有作用。

oneway

该设置表示当前调用是否不等待返回值。当该设置为 true 时,请求发送之后,并不等待服务器返回结果,回调函数将立即被调用,结果被设置为 undefined

sync

该设置表示当前调用是否为“同步”调用。这里的“同步”调用是伪同步。它仅表示在该调用之后,同一个客户端所发起的其它远程调用一定是在本次调用执行完之后才会被调用。它可以保证几个连续的调用将按书写顺序执行。但每个调用本身还是异步的。

onsuccess

该设置表示调用成功时的回调函数,跟 invoke 方法的 onsuccess 参数是一个意思。它通常不会在 settings 参数中设置,因为在没有 onsuccess 参数的情况下,无法传递 settings 参数。但是如果在 settings 参数中也设置了该属性,那么它将会覆盖 invoke 方法的 onsuccess 参数的设置。

onerror

该设置表示调用失败时的回调函数,跟 invoke 方法的 onerror 参数是一个意思。它通常也不会在 settings 参数中设置。但是如果在 settings 参数中也设置了该属性,那么它将会覆盖 invoke 方法的 onerror 参数的设置。

useHarmonyMap

该设置跟前面介绍的 useHarmonyMap 属性功能相同,但只针对当前调用有效。

userdata

该属性是一个对象,它用于存放一些用户自定义的数据。这些数据可以通过 context 对象在整个调用过程中进行传递。当你需要实现一些特殊功能的 Filter 或 Handler 时,可能会用到它。

上面这些设置除了可以作为 settings 参数的属性传入以外,还可以在远程方法上直接进行属性设置,这些设置会成为 settings 参数的默认值。例如上面那个引用参数传递的例子还可以写成这样:

var hprose = require('hprose');
var client = hprose.Client.create('http://www.hprose.com/example/',
                                 ['swapKeyAndValue']);
var weeks = {
    'Monday': 'Mon',
    'Tuesday': 'Tue',
    'Wednesday': 'Wed',
    'Thursday': 'Thu',
    'Friday': 'Fri',
    'Saturday': 'Sat',
    'Sunday': 'Sun',
};

client.swapKeyAndValue.onsuccess = function(result, args) {
    console.log(weeks.constructor, weeks);
    console.log(result.constructor, result);
    console.log(args.constructor, args);
};

client.swapKeyAndValue.byref = true;

client.swapKeyAndValue(weeks);

运行结果是一样的。这里就不在重复了。

invoke 方法的链式调用

因为 invoke 方法的返回值是一个 promise 对象,因此它可以进行链式调用,例如:

var hprose = require('hprose');
var client = hprose.Client.create('http://www.hprose.com/example/', ['sum']);
client.sum(1, 2)
      .then(function(result) {
          return client.sum(result, 3);
      })
      .then(function(result) {
          return client.sum(result, 4);
      })
      .then(function(result) {
          console.log(result);
      });

该程序的结果为:

10

比链式调用更简单的顺序调用

前面我们讲过,当使用方法名调用时,远程调用的参数本身也可以是 promise 对象。

因此,上面的链式调用还可以直接简化为:

var hprose = require('hprose');
var client = hprose.Client.create('http://www.hprose.com/example/', ['sum']);
client.sum(client.sum(client.sum(1, 2), 3), 4).then(function(result) {
    console.log(result);
});

这比上面的链式调用更加直观。尤其是当一个调用的参数依赖于其它几个调用的结果时候,例如:

var hprose = require('hprose'),
    wrap = hprose.Future.wrap,
    client = hprose.Client.create('http://www.hprose.com/example/', ['sum']),
    log = wrap(console.log, console),
    r1 = client.sum(1, 3, 5, 7, 9),
    r2 = client.sum(2, 4, 6, 8, 10),
    r3 = client.sum(r1, r2);
log(r1, r2, r3);

这个程序的运行结果为:

25 30 55

该程序虽然是异步执行,但是书写方式却是同步的,不需要写任何回调。

而且这里还有一个好处,r1r2 两个调用的参数之间没有依赖关系,是两个相互独立的调用,因此它们将会并行执行,而 r3 的调用依赖于 r1r2,因此 r3 会等 r1r2 都执行结束后才会执行。也就是说,它不但保证了有依赖关系的调用会根据依赖关系顺序执行,而且对于没有依赖的调用还能保证并行执行。

这是回调方式和链式调用方式都很难做到的,即使可以做到,也会让代码变得晦涩难懂。

这也是 hprose 2.0 最大的改进之一。

批处理调用

client.batch.begin();
...
client.batch.end([settings]);

通过这两个方法,可以实现批处理调用。例如:

var hprose = require('hprose');
var BytesIO = hprose.BytesIO;
var client = hprose.Client.create('http://www.hprose.com/example/', ['hello']);
function onsuccess(result) {
    console.log(result.constructor, BytesIO.toString(result));
}
client.batch.begin();
client.hello("World", onsuccess, { mode: hprose.Normal });
client.hello("World", onsuccess, { mode: hprose.Serialized });
client.hello("World", onsuccess, { mode: hprose.Raw });
client.hello("World", onsuccess, { mode: hprose.RawWithEndTag });
client.batch.end({ idempotent: true });

运行结果为:

[Function: String] 'Hello World'
[Function: Uint8Array] 's11"Hello World"'
[Function: Uint8Array] 'Rs11"Hello World"'
[Function: Uint8Array] 'Rs11"Hello World"z'

需要注意的是,虽然这里结果的显示顺序跟调用的顺序完全相同,但是这并不代表在服务器端这些方法是顺序执行的。

批处理调用的功能是可以在一个请求中同时发送多个调用,这多个调用在服务器端并发执行,最后汇总结果一次性返回。

因此,批处理调用的方法之间不能有前后依赖的顺序。

因此前面这个例子:

var hprose = require('hprose'),
    wrap = hprose.Future.wrap,
    client = hprose.Client.create('http://www.hprose.com/example/', ['sum']),
    log = wrap(console.log, console),
    r1 = client.sum(1, 3, 5, 7, 9),
    r2 = client.sum(2, 4, 6, 8, 10),
    r3 = client.sum(r1, r2);
log(r1, r2, r3);

r3r1r2 之间有先后依赖关系,因此,这三个调用是不能同时放在批处理调用中的。 但是 r1r2 之间并没有依赖关系,所以,它们两个可以放在批处理调用中。

另外需要注意的一点是,在批处理调用的设置中,以下选项仅在每个单独的调用中设置有效:

  • mode
  • byref
  • simple
  • onsuccess
  • onerror
  • useHarmonyMap

以下选项仅在 endBatch 方法中设置有效:

  • failswitch
  • timeout
  • idempotent
  • retry
  • oneway
  • sync

另外,还有一个 userdata 选项比较特殊,它在两个里面都可以设置,但两处设置所在的上下文是不同的。


⚠️ 在 hprose 1.4 到 1.6 的 Node.js 版本中也实验性的加入了批处理调用支持,旧版本中批处理的 API 是 beginBatchendBatch 这两个方法,这两个方法都没有参数,这个功能在这些旧版本中有很多限制,因此不推荐在旧版本中使用该功能。在 hprose 2.0 中,beginBatchendBatch 这两个方法已废止。请使用新的 batch.beginbatch.end 方法来代替。


subscribe 方法

client.subscribe(topic, callback[, timeout]);
client.subscribe(topic, id, callback[, timeout]);

subscribe 方法的用处是订阅服务器端的推送服务。该方法有两种方式,一种是自动获取设置客户端 id,另一种是手动设置客户端 id

参数 topic 是订阅的主题名,它实际上也是一个服务器端的方法,该方法与普通方法的区别是,它只有一个参数 id,该参数表示客户端的唯一编号,该方法的返回值即推送信息,当返回值为 null 或者抛出异常时,客户端会忽略并再次调用该 topic 方法。当该方法返回推送消息时,callback 回调函数会执行,并同时再次调用该 topic 方法。因此当没有推送消息时,该方法不应该立即返回值,而应该挂起等待,直到超时或者有推送消息时再返回结果。

当然,对于开发者来说,自己实现一个完善的推送方法还是有一定难度的。因此,hprose 2.0 的服务器端已经提供了一整套的专门用于推送的 API,通过这些 API,可以方便的自动实现用于推送的服务方法。在后面介绍服务器端时,我们再介绍这部分内容。

参数 id 是客户端的唯一编号,如果省略的话,客户端会自动通过 clientid 属性来获取,如果该属性未初始化,会自动调用一个名字为 # 的服务器端远程方法,之所以使用这个特殊的名字是为了防止跟用户发布的普通方法发生冲突。hprose 2.0 服务器已经自动实现了该方法,但是用户也可以用自己的实现来替换它,它的默认实现是一个整数自增计数器。当用户指定了 id 参数时,客户端会将它作为该 topic 方法的参数值传给服务器端,但不会修改客户端的 id 属性值。

参数 callback 是用来处理推送消息的回调函数,该参数不能省略。

参数 timeout 是等待推送消息的超时时间,单位是毫秒(ms),可以省略,默认值与 timeout 属性值相同。超时之后并不会产生异常,而是重新发起对 topic 方法的调用。因此,如果用户要在服务器端自己实现推送方法,应当注意处理好同一个客户端对同一个推送方法可能会进行重复调用的问题。如果使用 hprose 2.0 提供的推送 API,则不需要关心这一点。

对于同一个推送主题,subscribe 方法允许被多次调用,这样可以对同一个推送主题指定多个不同的回调方法。但通常没有必要也不推荐这样做。

unsubscribe 方法

client.unsubscribe(topic)
client.unsubscribe(topic, callback)
client.unsubscribe(topic, id)
client.unsubscribe(topic, id, callback)

该方法用于取消订阅推送主题。当调用该方法时,带有 callback 参数,将只取消对该 callback 方法的回调,除非这是该主题上最后一个 callback,否则对该主题远程方法的调用并不会中断。当所有的 callback 都被取消之后,或者当调用该方法时,没有指定 callback 参数时,将会中断对该主题远程方法的循环调用。

如果 id 参数若未指定,那么当客户端 id 属性有值时,将只取消对该 id 属性值对应的推送主题的订阅。当客户端 id 属性未初始化时,将会取消该主题上所有的订阅。

通常来说,当你调用 subscribe 方法时如果指定了 id 参数,那么当调用 unsubscribe 方法时你也应该指定相同的 id 参数。当你调用 subscribe 方法时没有指定 id 参数,那么当调用 unsubscribe 方法时你也不需要指定 id 参数。

Clone this wiki locally