接下来我们来看下jQuery的延迟对象Deferred。
我们前面讲过Callbacks函数,其实Deferred就是在此基础进行了扩展,Deferred是对异步函数的统一管理。
源码
/**源码2999行**/
jQuery.extend({
Deferred: function( func ) {
var tuples = [
// 类似Callbacks的fire, 类似Callbacks的add, Callbacks, 执行完毕的状态
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
[ "notify", "progress", jQuery.Callbacks("memory") ]
],
state = "pending",//没有执行之前状态默认为state
promise = {
state: function() {
return state;
},
always: function() {
deferred.done( arguments ).fail( arguments );
return this;
},
then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
return jQuery.Deferred(function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
var action = tuple[ 0 ],
fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
// deferred[ done | fail | progress ] for forwarding actions to newDefer
deferred[ tuple[1] ](function() {
var returned = fn && fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
} else {
newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
});
fns = null;
}).promise();
},
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function( obj ) {
return obj != null ? jQuery.extend( obj, promise ) : promise;
}
},
deferred = {};
// Keep pipe for back-compat
promise.pipe = promise.then;
// Add list-specific methods
jQuery.each( tuples, function( i, tuple ) {
var list = tuple[ 2 ],
stateString = tuple[ 3 ];
// promise[ done | fail | progress ] = list.add
promise[ tuple[1] ] = list.add;
// Handle state
if ( stateString ) {
list.add(function() {
// state = [ resolved | rejected ]
state = stateString;
// [ reject_list | resolve_list ].disable; progress_list.lock
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}
// deferred[ resolve | reject | notify ]
deferred[ tuple[0] ] = function() {
deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
return this;
};
deferred[ tuple[0] + "With" ] = list.fireWith;
});
// Make the deferred a promise
promise.promise( deferred );
// Call given func if any
if ( func ) {
func.call( deferred, deferred );
}
// All done!
return deferred;
}
});
根据代码我们能够解构出该函数主要的两个对象promise 和 deferred
promise解构
promise = {
state,
always,
pipe = then,
promise,
done,
fail,
progress
}
deferred解构
deferred = {
state,
always,
pipe = then,
promise,
done,
fail,
progress,
resolve,
reject,
notify,
resolveWith,
rejectWith,
notifyWith
}
相信看到这里很多理解了Callbacks的同学已经发现了,其实deferred 和 promise的区别其实就是,deferred对象提供了Callbacks的"fire"方法,而promise只有Callbacks的"add"。不过肯定很多同学肯定有跟我一样的疑惑,为什么要这样设计呢?其实也是比较好理解的,就拿ajax来说吧,打比方说我们ajax获取数据是否成功的状态能够被我们自己改变吗,答案是不能。所以deferred是提供给"内部方法"使用的,如果给"外部"使用我们就返回promise对象。例如:
function getDef(){
var def = $.Deferred();
//模拟延迟操作
setTimeout(function(){
def.resolve();
}, 1000);
return def.promise();
}
var def = getDef();
def.done(function(){
console.log(1);
});
这个时候getDef()所返回的promise已经不包含'fire'方法,所以状态不会被随便串改。
假设我们上面getDef函数直接返回def,是不是会存在下面这种情况
function getDef(){
var def = $.Deferred();
//模拟延迟操作
setTimeout(function(){
def.resolve();
}, 1000);
return def;
}
var def = getDef();
def.done(function(){
console.log(1);
});
def.resolve();
//发现程序一执行控制台就输出1了。
接下来我们来具体看一下源码是怎么实现的吧。
/**源码3046行**/
var tuples = [
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
[ "notify", "progress", jQuery.Callbacks("memory") ]
]
/**源码3051行**/
jQuery.each( tuples, function( i, tuple ) {
var list = tuple[ 2 ],
stateString = tuple[ 3 ];
// promise[ done | fail | progress ] = list.add
/**源码3056行**/
promise[ tuple[1] ] = list.add;
// Handle state
/**源码3059行**/
if ( stateString ) {
list.add(function() {
// state = [ resolved | rejected ]
state = stateString;
// [ reject_list | resolve_list ].disable; progress_list.lock
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}
// deferred[ resolve | reject | notify ]
/**源码3069行**/
deferred[ tuple[0] ] = function() {
deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
return this;
};
deferred[ tuple[0] + "With" ] = list.fireWith;
});
首先来看下tuples变量吧
1、resolve、reject、notify 等于 Callbacks 方法中提供的fire方法。源码3069行进行的赋值。
2、done、fail、progress 等于Callbacks 方法中提供的add方法。源码3056行进行的赋值。
3、源码3059行,会发现源码对 state = [ resolved | rejected ] 的list,add了方法,其实这段代码的意思是指 当执行玩resolve和reject,对state的状态进行修改,如果执行完resolve(成功)就不能再执行reject(失败)了。并对进行中的list进行了锁定操作。
接下来我们来看一下稍微复杂点的then方法,首先贴上源码
/**源码3017行**/
then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
return jQuery.Deferred(function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
var action = tuple[ 0 ],
fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
// deferred[ done | fail | progress ] for forwarding actions to newDefer
/**源码3024行**/
deferred[ tuple[1] ](function() {
var returned = fn && fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
/**源码3071行**/
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
} else {
/**源码3076行**/
newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
});
fns = null;
}).promise();
}
了解源码之前我们先来看看then有哪些写法,首先是第一种比较简单的写法,可以分别传3个回调函数分别对应3个状态的回调函数
function getDef(){
var def = $.Deferred();
//模拟延迟操作
setTimeout(function(){
def.resolve();
//def.reject();
//def.notify();
}, 1000);
return def;
}
var def = getDef();
def.then(function(){
console.log('成功');
}, function(){
console.log('失败');
}, function(){
console.log('进行中');
});
该功能的实现代码是在源码的3024行实现的,将传入的三个函数分别add [done | fail | progress] 到对应的list中,最后直接通过var returned = fn && fn.apply( this, arguments );这句来执行回调函数。
then的第二种使用方法
function getDef(){
var def = $.Deferred();
//模拟延迟操作
setTimeout(function(){
def.resolve();
// def.resolve();
// def.notify();
}, 1000);
return def;
}
var def = getDef();
def.then(function(){
console.log('成功');
return '123';
}, function(){
console.log('失败');
}, function(){
console.log('进行中');
}).then(function(a){
console.log(a);
}, function(){
console.log('失败1');
}, function(){
console.log('进行中1');
});
then每次执行完毕之后都会返回一个promise(包含then方法),所以一直通过then进行链接。然后通过源码3076行进行参数传递。如果return的是一个Deferred对象,则可以通过3071行实现链式写法。例如:
function getDef(){
var def = $.Deferred();
//模拟延迟操作
setTimeout(function(){
def.resolve();
// def.resolve();
// def.notify();
}, 1000);
return def.promise();
}
var def = getDef();
def.then(function(){
console.log('成功');
var def1 = getDef();
return def1;
}).done(function(){
console.log('成功1');
});
程序执行1秒之后打印成功,然后再过一秒打印出成功1