1.5 Hprose 客户端
Hprose 2.0 for javascript 支持多种底层网络协议绑定的客户端,比如:HTTP 客户端,TCP 客户端和 WebSocket 客户端。
其中 HTTP 客户端支持跟 HTTP、HTTPS 绑定的 hprose 服务器通讯。
TCP 客户端支持跟 TCP 绑定的 hprose 服务器通讯,并且支持全双工和半双工两种模式。
WebSocket 客户端支持跟 ws、wss 绑定的 hprose 服务器通讯。
尽管支持这么多不同的底层网络协议,但除了在对涉及到底层网络协议的参数设置上有所不同以外,其它的用法都完全相同。因此,我们在下面介绍 hprose 客户端的功能时,若未涉及到底层网络协议的区别,就以 HTTP 客户端为例来进行说明。
创建客户端
创建客户端有两种方式,一种是直接使用构造器函数,另一种是使用工厂方法 create
。
使用构造器函数创建客户端
hprose.Client
是一个抽象类,因此它不能作为构造器函数直接使用。如果你想创建一个具体的底层网络协议绑定的客户端,你可以将它作为父类,至于如何实现一个具体的底层网络协议绑定的客户端,这已经超出了本手册的内容范围,这里不做具体介绍,有兴趣的读者可以参考 hprose.HttpClient
、hprose.TcpClient
和 hprose.WebSocketClient
这几个底层网络协议绑定客户端的实现源码。
hprose.HttpClient
、hprose.TcpClient
和 hprose.WebSocketClient
这三个函数是可以直接使用的构造器函数。它们分别对应 http 客户端、tcp 客户端和 WebSocket 客户端。
new hprose.HttpClient([uri[, functions[, settings]]]);
new hprose.TcpClient([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
- binary
这些设置都有对应的客户端设置方法,这里暂不解释,在后面介绍设置方法时,再分别介绍。
例如:
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
对象上,就有两个远程方法,他们分别是 hello
和 sum
。
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.add
、user.update
、user.del
和user.get
分别对应服务器端发布的别名为:user_add
, user_update
, user_del
和 user_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
方法。
在省略函数名列表的情况下,对于 user_add
这样的方法,在旧版本中是不能使用 user.add
的方式调用的。但是从 hprose-js v2.0.33 版本之后,也可以使用 user.add
这种方式了。
通过工厂方法 create
创建客户端
hprose.Client.create(uri[, functions[, settings]]);
hprose.HttpClient.create(uri[, functions[, settings]]);
hprose.TcpClient.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 地址没有区别,支持 http
和 https
两种协议,这里不做介绍。
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 协议。
tls
和 tcps
意义相同,表示安全的 tcp 协议,地址可以是 ipv6 地址,也可以是 ipv4 地址。如有必要,可设置客户端安全证书。
tcp4s
表示地址为 ipv4 的安全的 tcp 协议。
tcp6s
表示地址为 ipv6 的安全的 tcp 协议。
客户端事件属性
onerror 事件
该属性为函数类型,默认值为空函数(即无任何操作的函数)。
当客户端调用发生错误时,如果没有为调用设置异常处理回调函数,则该属性将被回调。回调函数有两个参数,第一个参数是方法名(字符串类型),第二个参数是调用中发生的错误(通常为 Error 对象)。
onfailswitch 事件
该属性为函数类型,默认值为空函数(即无任何操作的函数)。
当调用的 failswitch
属性设置为 true
时,如果在调用中出现网络错误,进行服务器切换时,该事件会被触发。该事件的回调函数只有一个参数,即客户端对象本身。
客户端方法
uri 方法
client.uri();
该方法返回客户端当前所使用的地址。如果客户端在创建时设置了多个服务地址,该属性的值仅为这多个地址中当前正在使用中的那个地址。
uriList 方法
client.uriList(value);
client.uriList();
有参数时为设置 uriList
的值,无参数时返回 uriList
的设置值,该方法的参数和返回值为字符串数组类型。
该方法表示当前客户端可以调用的服务地址列表。
id 方法
client.id();
该方法返回当前客户端在进行推送订阅时的唯一编号。在没有进行推送订阅或者使用自己指定 id 方式进行推送订阅时,该方法返回值为 null。你也可以调用 client['#']();
方法来手动从服务器端获取该 id
的值。
binary 方法
client.binary(value);
client.binary();
有参数时为设置 binary
的值,无参数时返回 binary
的设置值,该方法的参数和返回值为 Boolean
类型。默认值为 false
。
该方法表示当前客户端的调用是否允许传输二进制数据。
你也可以针对某个调用进行单独设置。
failswitch 方法
client.failswitch(value);
client.failswitch();
有参数时为设置 failswitch
的值,无参数时返回 failswitch
的设置值,该方法的参数和返回值为 Boolean
类型。默认值为 false
。
该方法表示当前客户端在因网络原因调用失败时是否自动切换服务地址。当客户端服务地址仅设置一个时,不管该属性值为何,都不会切换地址。
你也可以针对某个调用进行单独设置。
failround 方法
client.failround();
该方法默认返回值为 0。当调用中发生服务地址切换时,如果服务列表中所有的服务地址都切换过一遍之后,该方法返回值会加 1。你可以根据该方法的返回值来决定是否更新服务列表。更新服务列表可以使用 uriList
方法。
timeout 方法
client.timeout(value);
client.timeout();
有参数时为设置 timeout
的值,无参数时返回 timeout
的设置值,该方法的参数和返回值为整数类型,默认值为 30000
,单位是毫秒(ms)。
该方法表示当前客户端在调用时的超时时间,如果调用超过该时间后仍然没有返回,则会以超时错误返回。
你也可以针对某个调用进行单独设置。
idempotent 方法
client.idempotent(value);
client.idempotent();
有参数时为设置 idempotent
的值,无参数时返回 idempotent
的设置值,该方法的参数和返回值为 Boolean
类型,默认值为 false
。
该方法表示调用是否为幂等性调用,幂等性调用表示不论该调用被重复几次,对服务器的影响都是相同的。幂等性调用在因网络原因调用失败时,会自动重试。如果 failswitch
属性同时被设置为 true
,并且客户端设置了多个服务地址,在重试时还会自动切换地址。
你也可以针对某个调用进行单独设置。
retry 方法
client.retry(value);
client.retry();
有参数时为设置 retry
的值,无参数时返回 retry
的设置值,该方法的参数和返回值为整数类型,默认值为 10
。
该方法表示幂等性调用在因网络原因调用失败后的重试次数。只有 idempotent
属性为 true
时,该属性才有作用。
你也可以针对某个调用进行单独设置。
byref 方法
client.byref(value);
client.byref();
有参数时为设置 byref
的值,无参数时返回 byref
的设置值,该方法的参数和返回值为 Boolean
类型,默认值为 false
。
该方法表示调用是否为引用参数传递。当设置为引用参数传递时,服务器端会传回修改后的参数值(即使没有修改也会传回)。因此,当不需要该功能时,设置为 false
会比较节省流量。
你也可以针对某个调用进行单独设置。
simple 方法
client.simple(value);
client.simple();
有参数时为设置 simple
的值,无参数时返回 simple
的设置值,该方法的参数和返回值为 Boolean
类型,默认值为 false
。
该方法表示调用中所传输的数据是否为简单数据。简单数据是指:null、数字(包括整数、长整数、浮点数)、Boolean 值、字符串、二进制数据、日期时间等基本类型的数据或者不包含引用的数组、Map 和对象。当该属性设置为 true
时,在进行序列化操作时,将忽略引用处理,加快序列化速度。但如果数据不是简单类型的情况下,将该属性设置为 true
,可能会因为死循环导致堆栈溢出的错误。
简单的讲,用 JSON
可以表示的数据都是简单数据。但是对于比较复杂的 JSON
数据,设置 simple
为 true
可能不会加快速度,反而会减慢,比如对象数组。因为默认情况下,hprose 会对对象数组中的重复字符串的键值进行引用处理,这种引用处理可以对序列化起到优化作用。而关闭引用处理,也就关闭了这种优化。
你也可以针对某个调用进行单独设置。
因为不同调用的数据可能差别很大,因此,建议不要修改默认设置,而是针对某个调用进行单独设置。
useHarmonyMap 方法
client.useHarmonyMap(value);
client.useHarmonyMap();
有参数时为设置 useHarmonyMap
的值,无参数时返回 useHarmonyMap
的设置值,该方法的参数和返回值为 Boolean
类型,默认值为 false
。
该方法表示调用所返回的数据中,如果包含有 Map 类型的数据,是否反序列化为 ECMAScript 6 中的 Map
类型对象。当该属性设置为 false
时(即默认值),Map 类型的数据将会被反序列化为 Object
实例对象的数据。
除非 Map 中的键不是字符串类型,否则没必要将该属性设置为 true
。
你也可以针对某个调用进行单独设置。
keepAlive 方法
client.keepAlive(value);
client.keepAlive();
有参数时为设置 keepAlive
的值,无参数时返回 keepAlive
的设置值,该方法的参数和返回值为 Boolean
类型,默认值为 true
。
该方法表示客户端和服务器端之间是否保持长连接。该属性对 TCP 和 WebSocket 客户端有效。
filter 方法
client.filter(value);
client.filter();
有参数时为设置 filter
的值,无参数时返回 filter
的设置值,该方法的参数和返回值可以为对象类型或对象数组类型。默认值为 null。
该属性的作用是可以设置一个或多个 Filter
对象。关于 Filter
对象,我们将作为单独的章节进行介绍,这里暂且略过。
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);
该方法的用处是对于未初始化的客户端对象,进行后期初始化设置,或者用于变更服务器地址。
当未设置任何参数调用时,该客户端返回一个 promise
对象,该 promise
对象的成功值为远程服务代理对象。
当仅设置了 uri
参数时,跟上面的功能相同,但是会替换当前的 uri
设置。注意,这里的 uri
地址只能是单个的服务地址,而不能是服务地址数组列表。
functions
参数是一个服务方法列表,与创建客户端时的 functions
参数相同,但不能是单个的字符串方法名。当设置了该列表参数后,会直接返回一个远程服务代理对象。
例如:
var client = new hprose.HttpClient('http://www.hprose.com/example/', ['hello']);
client.hello("World", function(result) { console.log(result); });
跟下面的代码的效果完全相同。
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
对象。
该方法与直接使用远程方法名调用功能类似。但有以下几点区别:
- 直接使用远程方法名调用时,参数
arg1
...argn
中的参数可以是promise
对象。直接使用invoke
方法时,args 是参数数组,里面的元素不可以包含promise
对象。 - 当同时使用远程方法名和
invoke
方法进行远程调用时,不管哪个写在前面,都是invoke
方法先执行,原因是使用远程方法名调用时,会先对对参数中的promise
对象进行取值操作,而invoke
方法调用不会有这个过程。 invoke
方法的args
必须是数组类型,但可以省略,省略的话,被认为该调用没有参数。invoke
方法的onsuccess
在省略的情况下,仍然可以带入settings
参数。但直接使用远程方法名调用时,如果onsuccess
省略,则不能带入settings
参数。
settings
参数可以包括以下设置:
mode
binary
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 client = hprose.Client.create('http://www.hprose.com/example/', ['hello']);
function onsuccess(result) {
console.log(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
设置,对于该设置在后面再做详细解释。
该程序执行结果如下:
>
'Hello World'
's11"Hello World"'
'Rs11"Hello World"'
'Rs11"Hello World"z'
>
由于历史原因,为了兼容旧版本的 hprose 的写法,该设置也可以不写在 settings
对象中,例如上面程序还可以这样写:
var client = hprose.Client.create('http://www.hprose.com/example/', ['hello']);
function onsuccess(result) {
console.log(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 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
之后也是可以的。
binary
该设置表示本次调用中所传输的参数是否包含二进制数据。前面在方法介绍中已经进行了说明,这里就不在重复。
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 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 方法的返回值是一个 promise
对象,因此它可以进行链式调用,例如:
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 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 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
>
该程序虽然是异步执行,但是书写方式却是同步的,不需要写任何回调。
而且这里还有一个好处,r1
和 r2
两个调用的参数之间没有依赖关系,是两个相互独立的调用,因此它们将会并行执行,而 r3
的调用依赖于 r1
和 r2
,因此 r3
会等 r1
和 r2
都执行结束后才会执行。也就是说,它不但保证了有依赖关系的调用会根据依赖关系顺序执行,而且对于没有依赖的调用还能保证并行执行。
这是回调方式和链式调用方式都很难做到的,即使可以做到,也会让代码变得晦涩难懂。
这也是 hprose 2.0 最大的改进之一。
批处理调用
client.batch.begin();
...
client.batch.end([settings]);
通过这两个方法,可以实现批处理调用。例如:
var client = hprose.Client.create('http://www.hprose.com/example/', ['hello']);
function onsuccess(result) {
console.log(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 });
运行结果为:
>
'Hello World'
's11"Hello World"'
'Rs11"Hello World"'
'Rs11"Hello World"z'
>
需要注意的是,虽然这里结果的显示顺序跟调用的顺序完全相同,但是这并不代表在服务器端这些方法是顺序执行的。
批处理调用的功能是可以在一个请求中同时发送多个调用,这多个调用在服务器端并发执行,最后汇总结果一次性返回。
因此,批处理调用的方法之间不能有前后依赖的顺序。
因此前面这个例子:
var 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);
r3
和 r1
、r2
之间有先后依赖关系,因此,这三个调用是不能同时放在批处理调用中的。 但是 r1
和 r2
之间并没有依赖关系,所以,它们两个可以放在批处理调用中。
另外需要注意的一点是,在批处理调用的设置中,以下选项仅在每个单独的调用中设置有效:
- mode
- byref
- simple
- onsuccess
- onerror
- useHarmonyMap
以下选项仅在 endBatch
方法中设置有效:
- binary
- failswitch
- timeout
- idempotent
- retry
- oneway
- sync
另外,还有一个 userdata
选项比较特殊,它在两个里面都可以设置,但两处设置所在的上下文是不同的。
:warning: 在 hprose 1.4 到 1.6 的 JavaScript 版本中也实验性的加入了批处理调用支持,旧版本中批处理的 API 是
beginBatch
和endBatch
这两个方法,这两个方法都没有参数,这个功能在这些旧版本中有很多限制,因此不推荐在旧版本中使用该功能。在 hprose 2.0 中,beginBatch
和endBatch
这两个方法已废止。请使用新的batch.begin
和batch.end
方法来代替。