最近懒癌发做,说好的系列文章,写了一半,一直懒得写,今天补上一篇。segmentfault
Deferred
咱们在使用promise
对象时,总会提到一个与它关系密切的对象——Deferred
。其实Deferred
没什么内容可讲的,其实很简单。数组
它包含一个promise
对象promise
它能够改变对应的promise
的状态app
简单的实现以下:ide
class Deferred{ constructor(){ let defer = {}; defer.promise = new Promise((resolve, reject)=>{ defer.resolve = resolve; defer.reject = reject; }) return defer; } }
咱们知道promise
对象内部的状态,自己是在建立对象时传入的函数内控制,外部是访问不到的,Deferred
对象在它的基础上包装了一层,并提供了两个在外部改变它状态的方法。函数
用法其实在Promise介绍--规范篇中的例子内,多处使用到了,这里就再也不赘述。总以为文章写这么点儿,就显得太水了。。因此借此,讲讲jQuery
中的实现。this
jQuery
中还有一个静态方法$.Callbacks()
,因为$.Deferred()
强依赖它,因此咱们先从它开刀。spa
$.Callbacks()
$.Callbacks()
咱们称为回调对象,它内部会维护一个数组,咱们能够向其中添加若干个回调函数,而后在某一条件下触发执行。code
有几个方法从名字咱们就知道它的做用是什么,add
向数组内部添加一个回调函数,empty
清空数组,fire
触发回调函数,has
数组中是否已经添加某回调函数,remove
从数组中删除某回调函数。对象
fireWith
函数接收两个参数,第一个是回调函数执行的上下文,第二个是回传给回调函数的参数。fire
中其实内部调用的就是fireWith
,其中第一个参数传递的是this
。
其它的几个函数,都和回调数组的状态有关。建立Callbacks
对象时,接收一个字符串或者对象做为参数。其实内部都会转换为对象,这里不赘述,不一样字符串表示不一样的处理方式,一一介绍。
once
:对象只会调用一次。
let cb = $.Callbacks('once') function a(){console.log('a')} function b(){console.log('b')} cb.add(a) cb.fire() cb.add(b) cb.fire() // a
第一次fire
以后,回调列表以后不会再次触发。
memory
: 记住回调列表的执行状态,若是回调函数fire
过一次,以后每次add
以后,则自动触发该回调。
let cb = $.Callbacks('memory') function a(){console.log('a')} function b(){console.log('b')} cb.add(a) cb.fire() // a cb.add(b) // b
第一次fire
以后,再次add
新的回调函数b
时,自动执行回调b
。
unique
:每个回调函数只能够添加一次。
let cb = $.Callbacks('unique') function a(){console.log('a')} function b(){console.log('b')} cb.add(a) cb.add(a) cb.fire() // a cb.add(b) cb.fire() // a // b
第一次fire
时,只会打印一个a
,说明第二个a
没有添加成功,但当咱们添加b
时,是能够添加成功的。
stopOnFalse
:当前面的回调函数返回false
时,终止后面的回调继续执行。
let cb = $.Callbacks('stopOnFalse') function a(){console.log('a');return false;} function b(){console.log('b')} cb.add(a) cb.add(b) cb.fire() // a
函数a
返回了false
,致使函数b
没有执行。
咱们再回过头看$.Callbacks()
对象的方法,lock
方法表示锁住回调数组,再也不执行,也就是模式为once
时,调用一次fire
后的状态,即在此以后不能够在此触发。以下:
let cb = $.Callbacks() function a(){console.log('a')} function b(){console.log('b')} cb.add(a) cb.fire() cb.lock() cb.add(b) cb.fire()
lock
以后,再次添加函数b
并调用fire
时,不会再次执行,与once
模式下效果相似。但若是是memory
模式,回调先fire
,而后再lock
,以后再次add
时,新添加的函数依然会执行。
let cb = $.Callbacks('memory') function a(){console.log('a')} function b(){console.log('b')} cb.add(a) cb.fire() cb.lock() cb.add(b) // a // b
其实这种效果和直接建立回调对象时,参数设为once memory
是一致的。也就是说,以下代码与上面效果一致。
let cb = $.Callbacks('once memory') function a(){console.log('a')} function b(){console.log('b')} cb.add(a) cb.fire() cb.add(b) // a // b
咱们发现这种once memory
模式,正好与Promise
中then
方法添加的回调很相似。若是promise
对象处于pending
状态,则then
方法添加的回调存储在一个数组中,当promise
对象状态改变(fire
)时,执行相应的回调,且以后再次经过then
方法添加回调函数,新回调会马上执行。同时,每个回调只能执行一次。因此,$.Deferred()
内部用的正好是once memory
的Callbacks
。
还有一个函数叫作disable
,它的做用是直接禁用掉这个回调对象,清空回调数组,禁掉fire
、add
等。
locked
用于判断数组是否被锁住,返回true
或false
。disabled
用于判断回调对象是否被警用,一样返回true
或false
。
$.Deferred()
有了以上的基础,咱们接下来看看jQuery
中,Deferred
对象的实现。咱们先看看它都有哪些方法。如图:
别的方法咱们暂且不关注,咱们注意到里面有四个咱们比较熟悉的方法,promise
,reject
,resolve
。它们和咱们前面说的Deferred
对象中做用差很少。
jQuery
的Deferred
中有三个添加回调函数的方法done
,fail
,progress
,分别对应添加promise
状态为resolved
、rejected
和pending
时的回调,同时对应有三个触发回调函数的方法resolve
、reject
和notify
。
咱们接下来看看它内部是怎么实现的。首先为每种状态分别建立一个Callbacks
对象,以下:
var tuples = [ // action, add listener, listener list, final state ["resolve", "done", jQuery.Callbacks("once memory"), "resolved"], ["reject", "fail", jQuery.Callbacks("once memory"), "rejected"], ["notify", "progress", jQuery.Callbacks("memory")] ]
咱们发现done
、fail
对应的回调对象是once memory
,而progress
对应的是memory
。说明经过progress
添加的函数,能够屡次重复调用。
而后定义了一个state
用来保存状态,以及内部的一个promise
对象。
state = "pending", promise = { state: function() { return state; }, always: function() { deferred.done(arguments).fail(arguments); return this; }, then: function( /* fnDone, fnFail, fnProgress */ ) { }, // 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; } },
接下来会执行一个循环,以下:
// 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][4].disable, tuples[2][5].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; });
这一段代码中咱们能够看出,done
、fail
、progress
其实就是add
,resolve
、reject
和notify
其实就是fire
,与之对应的resolveWith
、rejectWith
和notifyWith
其实就是fireWith
。且成功和失败的回调数组中,会预先添加一个函数,用来设置promise
的状态和禁用掉其它状态下的回调对象。
从上面这段代码中咱们也能够看出,添加回调函数的方法,都是添加在promise
对象上的,而触发回调的方法是添加在deferred
对象上的。代码中会经过以下方法,把promise
对象的方法合并到deferred
对象上。
promise.promise(deferred);
因此,咱们打印一下$.Deferred().promise()
。
发现它确实比$.Deferred()
少了那几个触发回调的方法。
其它的几个方法咱们简单说一下,always
会同时在成功和失败的回调数组中添加方法。state
是查看当前promise
对象的状态。
then
方法以下:
then: function( /* fnDone, fnFail, fnProgress */ ) { var fns = arguments; return jQuery.Deferred(function(newDefer) { jQuery.each(tuples, function(i, tuple) { var 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() .progress(newDefer.notify) .done(newDefer.resolve) .fail(newDefer.reject); } else { newDefer[tuple[0] + "With"]( this === promise ? newDefer.promise() : this, fn ? [returned] : arguments ); } }); }); fns = null; }).promise(); }
从代码中咱们能够看出,then
方法和规范中的then
方法相似,不过这里多了第三个参数,是用于给progress
添加回调函数,同时返回一个新的promise
对象。
pipe
是为了向前兼容,它与then
是相等的。
$.when()
jQuery
中还有一个与之相关的方法$.when()
,它的做用相似于Promise.all()
。具体实现方式基本是新建了一个Deferred
对象,而后遍历全部传递进去的promise
对象。不过添加了progres
的处理。总之和规范有不少的不一样,你们有兴趣的就本身看一下吧。