第三十一课:JSDeferred详解2
这一课,我们先接着上一课讲一下wait方法,以及wait方法是如何从静态方法变化实例方法的。
首先我们先看wait方法为啥可以从静态方法变成实例方法,请看register源码:
Deferred.register= function(name, fun){ //name="wait",fun=Deferred.wait;
this.prototype[name] = function(){ //this=Deferred,this.prototype[name] = Deferred.prototype.wait。
var a = arguments;
return this.next(function(){ return fun.apply(this,a)} )
};
};
然后我们来看wait方法为啥可以延迟执行fun2函数?
我们上一课的例子:Deferred.next(function fun1(){alert(1)}).wait(1).next(function fun2(){alert(2)}); 这里调用的wait方法其实是:
function(){
var a = arguments;
return this.next( function(){ return fun.apply(this,a) } ) //fun = Deferred.wait,this指的是第一个next方法(静态方法)返回的Deferred实例d1
};
因为你传入了1,所以a=1,然后执行this.next方法,因为这时的this是实例对象,因此调用实例对象的next方法。这时就会把function(){ return fun.apply(this,a)},添加给d1._next(Deferred实例对象d2)的callback.ok属性。返回d2。接着执行d2的next方法,返回d2._next(d3)。这时候,等静态方法next的定时器结束后(0秒后,实际是几毫秒后,当然每个浏览器不一样),执行队列上就会执行fun1方法,执行结束后,就会立即执行function(){ return fun.apply(this,a) },也就是执行Deferred.wait.apply(this,1),this是d2。
Deferred.wait = function(n){
var d= new Deferred() , t = new Date(); //这里又新建一个Deferred,我们称为d4.
var id = setTimeout(function(){
d.call((new Date()).getTime() - t.getTime());
}, n * 1000); //延迟一秒后,再执行d4.call(1000);这里的d4没有设置callback.ok属性值,因此使用它的默认值:Deferred.ok = function(x) { return x};因为这里延迟一秒后才执行,所以会执行下面的代码,也就是返回d4.这时候,你在_fire里面就可以看到,当next中添加的回调函数返回Deferred对象时,会执行这个操作value._next = this._next;也就是把 d4._next = d2._next。因此过了一秒后,执行d4的callback.ok方法,这时返回的值是1000,就会继续执行d4._next(d3)的回调函数fun2。
d.canceller = function(){
clearTimeout(id);
}
return d;
}
Deferred.register("wait",Deferred.wait); //这里就是把静态方法wait变成实例方法wait的。
因此,wait方法,可以延迟执行wait方法后面的next中添加的回调函数,非常有用,我们可以像时钟那样,想过多久执行这个函数,就过多久执行。
接下来,我们来看一下JSDeferred的并归结果:
JSDeferred最厉害之处,是它的parallel的实现。这个parallel是来解决这类问题的:有一个业务,需要发起4个Ajax请求,这4个请求的地址和返回时间不一样,必须等到它们都处理完成后,整合它们4个的返回数据,然后根据这个整合的数据再发起两个ajax请求,等到这两个ajax请求全部处理完成,整合这两个请求的数据后,再进行一次ajax请求,返回数据才算一次业务处理成功。
我们先来看一下它是如何使用的:
Deferred.parallel(function(){ return 1 },function(){ return 2 },function(){ return 3 }).next( function(a){ console.log(a); //[1,2,3] } )
上面这段代码的意思是:先执行第一个匿名函数,执行完后,再执行第二个匿名函数,接着再执行第三个匿名函数,等第三个执行完后,才会执行next中添加的匿名函数,而且可以接收到parallel中匿名函数返回的值。
我们再弄一个我们前端开发中经常会使用的操作:
function ajax(url){ //模拟ajax请求
var d = new Deferred();
var delay = url.match(/time=(\d+)/)[1]; // 此正则/time=(\d+)/,会取到字符串time=2000,因为match方法,所以会返回一个数组,数组第一项是匹配的整个字符串,也就是"time=2000",数组的第二项就是第一个子表达式取到的值,这里是2000(通过\d+取到的),因此delay = "2000"
setTimeout(function(){ //这里模拟ajax请求,也就是2秒后ajax请求结束,执行回调函数。
d.call(delay); //告诉parallel,我执行完了。只有等三个ajax方法,都执行完了后,才会执行next中添加的回调函数。
},+delay)
return d; //当第一个ajax方法执行到这里时,就会继续执行第二个ajax方法(不会等2秒后,才执行第二个ajax方法)。
}
Deferred.parallel(
ajax("chaojidan?time=2000"), //ajax请求1,花费2秒
ajax("chaojidan?time=3000"), //ajax请求2,花费3秒
ajax("chaojidan?time=4000") //ajax请求3,花费4秒
).next(function(a){
console.log(a); // a=[2000,3000,4000]
});
上面代码执行的顺序,先执行第一个ajax方法,但是ajax请求是需要时间的,那么第二个ajax方法会等到第一个ajax请求结束后(也就是ajax的请求完成,readyState等于4的时候),再执行(这时执行完parallel需要9秒的时间),还是等第一个ajax方法return后(也就是ajax请求刚开始,readyState=2,貌似opera浏览器没有状态2,这里忽略),就执行(这时执行完parallel需要4秒时间,取三个ajax请求时间花费最多的值)。毫无疑问,这里讲的是异步编程,所以parallel执行完需要4秒时间,三个ajax请求互不影响,但是必须等这三个ajax请求都结束后,才会执行next中添加的回调函数。这很容易实现上面的那个业务需求。
最后,我们来看parallel方法的源码解析:
Deferred.parallel = function(d2){
var isArray = false;
if(arguments.length > 1){ //如果参数大于1,就把参数弄成数组形式,我们的例子就是执行这里
d2 = Array.prototype.slice.call(arguments);
isArray = true;
}
else if(Array.isArray && Array.isArray(d2) ){ //如果传入的本身是一个数组
isArray = true;
}
var ret = new Deferred(),
values = {},
num = 0;
for(var i in d2){ //数组也可以使用for in方式来遍历
if(d2.hasOwnProperty(i)){ //数组有属性值0,1,2...
(function(d,i){ //第一个例子,这里的d就是function(){return 1},第二个例子,这里的d就是new出来的Deferred实例对象
if(typeof d == "function"){
d2[i] = d = Deferred.next(d); // 如果是函数,就转换成Deferred对象。这里调用的是静态方法next。因此过了浏览器的最小时钟间隔,就会马上执行d.next方法添加的回调函数。
}
d.next(function(v){ //如果传入的是Deferred实例对象d5,那么必须等到d5调用call方法后,才会执行这里next添加的回调函数。
values[i] = v;
if(--num <=0){ //只有等数组中的函数都执行完毕后,或者数组中的Deferred实例对象都调用了call方法后,就会进入if语句,执行ret.call。
if(isArray){
values.length = d2.length; //给json对象values添加length属性。把普通json转换成特殊json。
values = Array.prototype.slice.call(values,0); //特殊的json对象,可以通过数组的slice方法,转换成数组
}
ret.call(values);
}
});
num++; //根据上面的例子,这里的num最终会变成3。
})(d2[i],i);
}
}
if(!num){ //如果parallel方法中没有传入参数,那么就直接执行ret.call();
Deferred.next(function(){ //因为调用的是Deferred静态的next方法,因此过了浏览器的最小时钟间隔,就会立刻执行ret.call方法
ret.call();
})
}
return ret; //返回回去的ret对象,它的._next = d4(parallel方法后面的next方法创建的Deferred实例对象),因此只要调用了ret的call方法,就会执行next中添加的回调函数。
}
如果上一课没看懂,这一课基本上就是挂掉的,有点复杂,初学者莫看。
加油!