从今天开始,我会不定期的写一些关于JavaScript的东西,包括语言,应用等方面。组成JavaScript系列。
如果没有特殊的说明,这里假定JavaScript的执行环境是在浏览器(browser)当中的。
今天开始第一次,讨论一下同步和异步。
曾经查询过一些JavaScript的信息,发现google出来的结果都是询问JavaScript如何能够实现异步的代码。
而我,很不幸,查询的却是如何让JavaScript实现异步调用的同步(是不是挺起来很诡异)。
首先说一下JavaScript当中的异步方法。
其实这个问题是大家经常要碰到的。而且这个实现也很简单。我就不多说了。
给两段代码
- setTimeout方法,他让你的代码在指定的时间(毫秒)之后执行指定的方法。只执行一次。
比如:
alert(1);
setTimeout(”alert(2)”, 1000);
alert(3);
代码在执行到setTimeout的时候,会继续执行下面的代码(alert(3))而不会被阻塞。等待1000ms之后执行alert(2) - setInterval方法,他让你的代码每隔指定的时间,执行指定的方法,直到调用clearInterval
比如:
alert(1);
timer = setInterval(”alert(2)”, 1000);
alert(3);
代码基本上和上面的相同,不同的是,每隔1000ms就会执行一次alert(2),直到调用
clearInterval(timer);
我们应该注意到setTimeout和setInterval都是window的方法。
我们可以直接使用,但是规范的还是调用window.setTimeout window.setInterval,之所以提及这个,我会在以后的JavaScript系列中继续讲解。
现在该说一下我遇到的问题了。
我现在使用dwr作为AJAX的server端引擎,在调用dwr方法的时候,需要提供一个回调方法(callback function)来接受server的返回结果。
而这个回调方法是不会被阻塞的。此时browser回启动另外的现成处理。
这个很好理解,因为dwr的这个方法执行的时间是无法预料的,如果此时调用被阻塞,而server又花相当长的时间进行处理。那么浏览器就会死在这里。从用户体验的角度是根本无法接受的。
这里的例子代码是
…
ServerHandler.getString(”Weiming”, function (str) { //”Weiming”是传回server的参数
alert(str);
}); // ServerHandler是dwr提供的server方法的interface,具体使用请参见dwr网站。
alert(1);
在执行的过程中,会先执行alert(1),然后在一个无法预料的时间后执行alert(str)。
如果一次简单的比如hello world的调用是不会出问题的。
但是如果我要执行的一系列的dwr function是有前后顺序的,比如后面执行的需要前面的返回结果,简单的代码书写顺序是无法保证执行顺序的。
var myID = null;
ServerHandler.getID(function (id) {
myID = id; //无法预料何时会执行这句话
});
ServerHandler.getUserWithID(myID, function (name) {
/*
此时myID还没有值,因为上面的 myID = id这段代码是需要一个时间段之后才会执行的
*/
alert(”hello:” + name);
});
比如这样的代码就会出错。那么如何解决呢?
最简单的实现方法就是callback function的嵌套。
…
ServerHandler.getID(function (id) {
ServerHandler.getUserWithID(id, function (name) {
alert(”hello:” + name);
}
});
这样我们就可以保证多个dwr方法调用的顺序了。这样貌似解决了问题。但是并不完美。
原因是当我们把JavaScript和Browser作为一个操作的平台和逻辑业务的平台(AJAX的应用程序,后面的JavaScript系列中会有提及),而不是一个简单的展示平台的时候。这样的回调函数嵌套就很难控制了。
这也就是我最开始指出的需要同步异步调用的一个方法。
最终我在公司的解决方案是这样的。
写一个信号量的类(JavaScript的面向对象会稍后讲解),当我需要执行一个方法的时候,我就申请一部分信号量。
把需要被执行的方法放进信号量的队列进行等待。等前面等待的方法(如果存在)执行后在执行。
信号量将作为一个参数被传入执行的方法,这样这个方法可以决定释放这个信号量还是继续分发。
比如
var s = new Semaphore();
var myID = null;
s.p(function (e) {? //把方法放入信号量队列
ServerHandler.getID(function (id) {
myID = id;
s.v(); //释放信号量
}
});
s.p(function (e) { //将第二个方法放到信号量队列,只有当前面的s.v()执行之后,这个方法才会执行。
ServerHandler.getName(myID, function (name) { //此时,可以保证myID一定有值
alert(”Hello:” + name);
s.v();
})
})
这里只是对信号量这个方法进行了简单的阐述。
信号量还支持创建自信号量,如果创建了子信号量,那么父信号量必须等带所有的孩子都归还了信号量之后才可以执行他里面的代码。
由于代码的版权是公司的,所以很抱歉,现在无法给出相应的完整的信号量的实现。
如果下一端我有时间的话,我会给出一个我实现的版本的。