web前端之精通dojo六:异步编程
利用Dojo关联用户自定义事件
function f(){
console.log("hello word");
};
function myHandler(){
console.log("Hello from f's handler!");
};
var handler=dojo.connect("f",myHandler);
f();
关联方法:当f被调用的时候,可以看到控制台中显示两条信息:“hello word”,后面紧跟“Hello from f’s handler!”
关联用户定义事件
dojo.connect是一个重载函数,它将一个处理函数与一个DOM事件或一个触发函数进行连接,具体与谁连接取决于obj参数。
1.如果obj是一个DOM节点,那么event(一个字符串)必须是该DOM节点中已定义的一个DOM事件。
2.如果obj是一个对象而不是一个DOM节点,那么obj[event](event是一个字符串)必须指定一个函数,而这个函数用作触发函数
3.如果obj是null或缺失,那么dojo.global[event](event是一个字符串)必须指定了一个函数。而且这个函数用作触发函数。注意,event参数始终是一个字符串,这位dojo.connect确定obj是否被忽略提供了一种途径
下面是后两种用法的示例:
// 缺少obj,event是一个字符串
dojo.connect("functionName",context,handler);
// 等价于显示声明事件所有者为空
dojo.connect(null,"functionName",context,handler);
// 两种情况下,触发函数都是dojo.global["functionName"]
// 回忆一下,这里dojo.global引用全局对象空间
// 这里obj是一个非空非DOM节点的对象
dojo.connect(obj,"functionName",context,handler);
//触发函数是obj["functionName"]
当使用dojo.connect链接处理函数和触发函数时,触发函数执行完毕后,处理函数自动被调用,调用参数与触发函数的调用参数相同。不同于连接多个处理函数到一个DOM事件,当多个处理函数被连接到一个触发函数时,它们的调用顺序与它们的连接顺序相同。
dojo.connect的其他参数我们已经讨论过了。处理函数总是由dojo.hitch(context,handler)(context是可选的对象,handler是一个字符串或函数)计算出来的,而且以dojo.connect的返回值作为参数调用dojo.disconnect,可以取消处理函数的连接。
为了深入了解这些要点,我们给出一个简单的例子。定义一个printArgs函数,它只是把参数打印到控制台。然后将两个同样打印参数的控制器关联到printArgs。为了使输出内容直白易懂,我们令每一个函数都打印自己的名字。代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>关联用户定义事件</title>
<script type="text/javascript" src="../../../dojoroot/dojo/dojo.js" djConfig="parseOnLoad:true"></script>
<script type="text/javascript">
// 将functionName(一个字符串)和args(一个数组)中的所有元素打印到控制台
function giveMessage(functionName,args){
var message="In "+functionName+"; the arguments are: ";
dojo.forEach(args,function(arg){
message+=arg.toString()+" ";
});
console.log(message);
}
//这个作为事件被连接的函数
function printArgs(){
giveMessage("printArgs",arguments);
}
//这是第一个事件处理函数
function firstHandler(){
//这个处理函数使用参数对象获取它的参数
giveMessage("firstHandler",arguments);
}
//这是第二个处理函数
function secondHandler(a1,a2){
//这个处理函数使用参数对象获取它的参数
giveMessage("secondHandler",[a1,a2]);
}
//链接firstHandler/secondHandler,以便printArgs被调用后他也被触发
dojo.connect("printArgs",null,"firstHandler");
dojo.connect("printArgs",null,"secondHandler");
//测试
printArgs(2,3);
</script>
</head>
<body id="body">
</body>
</html>
订阅-发布
在订阅-发布模式中,一些函数注册他们感兴趣的“主题”。然后,许多进程可以发布关于该主题的信息。当然,在JavaScript程序的上下文中,发布的“消息”只是一个参数集合,“发布”动作就是以给定的参数调用每一个订阅者。Dojo提供了下面三个函数来实现这一模式。
handle=dojo.subscribe(topic,context,handler):当通过dojo.pulish发布的topic(主题,其类型是字符串)时,由dojo.hitch(context,handler)给出的函数被调用。返回值handle可以用来取消订阅,只要调用dojo.unsubscribe即可。
dojo.unsubscribe(handle):删除先前由dojo.subscribe建立的连接。handle是dojo.subscribe返回的对象
dojo.publish(topic,args):通过调用该主题的全部订阅者来发布topic(字符串),把args(数组)作为参数传给每一个订阅者。定义函数按照它们的订阅顺序被调用。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>发布-订阅</title>
<script type="text/javascript" src="../../../dojoroot/dojo/dojo.js" djConfig="parseOnLoad:true"></script>
<script type="text/javascript">
// 一个累加数值的简单对象
var numberAccumulator={
total:0
,add:function(x){
this.total += x;
}
};
//numberAccumulator.add订阅“Numbers”主题
dojo.subscribe("Numbers",numberAccumulator,"add");
//打印numberAccumulator.total
function showTotal(){
console.log("The total is:"+numberAccumulator.total);
}
//showTotal订阅“Numbers”主题
dojo.subscribe("Numbers",showTotal);
//测试,注意,参数是数组
dojo.publish("Numbers",[1]);
dojo.publish("Numbers",[2]);
</script>
</head>
<body id="body">
</body>
</html>
dojo.publish(“Numbers”,[1])引发numberAccumulator.add(1)和showTotal(1)被调用。上述代码显示dojo.publish的第二个参数必须是一个数组,他把这个数组的内容作为一个个参数传给订阅函数。由于showTotal并不需要函数,因此它忽略参数。
利用dojo.Deferred管理回调函数
实现进程控制
dojo.deferred它是Dojo中管理异步函数与一个或多个回调函数之间高级交互的一个类。
许多函数之间都是紧耦合的。每一个函数都从它之前的函数获取一些信息,执行代码,然后将信息传递给下一个函数。我们在display函数中定义所有其它函数,再通过共享一个局部变量来实现这种耦合。下面是我们的首次尝试:
// 实现一个显示引擎
// 在panelId指定的面板中显示通过的query获取的数据
function display(panelId,query){
//displayInfo是一个共享的薄记变量
var displayInfo={
panelId:panelId
,query:query
,processCompleteCount:0
};
//获取一个放置面板的HTML,div
// allocatePanel由我们应用程序的窗格管理逻辑提供
displayInfo.div=allocatePanel(displayInfo);
//TODO #1
//创建一个获取数据的异步调用
// 异步调用结束后
// 接着调用WithData
//TODO #2
//创建一个获取数据的异步调用
// 异步调用结束后
// 接着调用WithMetadata
//在不涉及元数据的情况下尽可能地处理函数
function continueWithData(data){
//把数据放入应用范围的缓存中,以便其他请求的使用,不需要等待元数据就可以执行
cacheData(data);
//保存数据的快速访问入口
displayInfo.data=data;
finishDisplay();
}
//在不涉及数据的情况下竟可能地处理元数据
function continueWithMetadata(metadata){
//建立HTML和其他元数据、提供的空间结构、不需要等待数据就可以执行
preparePanel(metadata);
//保持元数据的快速访问入口
displayInfo.metadata=metadata;
finishDisplay();
}
//当WithData和WithMetaData都完成时,finishDisplay结束
function finishDisplay(){
//TODO #3
}
}
至于那些TODO,我们要异步地从服务器查询数据/元数据,任意查询完毕之后传入所获得的数据/元数据,调用continueWithData/continueWithMetadata。这正是dojo.Deferred所要做的。
利用dojo.Deferred注册回调函数
dojo.Deferred是一个能够创建对象的构造函数,创建的对象管理下列代码之间的交互:异步函数与跟异步操作相关的其他客户代码的交互、包含一个或多个回调函数的函数链与异步操作相关的其他客户代码的交互。Deferred接口包括供异步函数发出正常或异常结束信号并返回结果的方法,以及其他一些方法使得客户代码得以实现以下功能:
1.申明在异步函数正常/非正常完成时被调用的一系列函数(即一系列回调函数)
2.检查异步函数是否完成,在完成的情况下访问并返回结果
3.取消对异步函数的等待
一个进程要进行一步调用的典型情况是,首先创建一个Deferred实例并为他指定异步函数,然后为其添加一个或多个回调函数,就可以不用管它了。
回到上面的例子,我们有两个独立的回调函数,continueWithData和cintinueWithMetaData,他们在两个异步函数获得数据和元数据后被调用。我们可以使用Deferred来建立这一关系:
//TODO #1部分实现
displayInfo.dataDeferred=new Dojo.Deferred();
displayInfo.dataDeferred.addCallback(continueWithData);
//TODO:
// 异步获取资源,并在完成后通知
// TODO #2部分实现
displayInfo.metadataDeferred=new dojo.Deferred();
displayInfo.metadataDeferred.addCallback(continueWithMetadata);
//TODO:
//异步获取元数据,并在完成后通知
上面的代码显示了要通过Deferred的addCallback方法注册回调。
后续还需继续学习