说到延迟对象deferred就不得不提,为什么会需要它,它用来干嘛的。
浏览器环境与后端的node.js一直存在着各种消耗巨大或阻塞线程的行为,但前端是不应该因此而阻塞的,所以基于javascript 的单线程机制,他的一种解耦的方式就是提供异步API。
何为异步API?
简单来说,它是不会立即执行的方法。例如浏览器提供的setTimeout和setInterval。后面再出现各种事件回调。再之后,就更多,XMLHttpRequest, postMessage 等等
这些东西有一个共同点,就是都拥有一个回调函数,用来描述接下来要干什么。
在业务复杂的情况下,就下造成各种回调嵌套的情况,以及随之而来的复杂代码。以致很难管理代码。
于是 Promise规范就出现了,然后jQuery.deferred就出现了。(Promise规范可以去geogle自行了解)
首先jQuery.defferred是基于jQuery.Callbacks实现的。这个待会再提,
首先我们得看看jQuery.defferred能给大家带来怎样的便利吧。
// 假设我们有多个异步行为 // 且我们需要在一个异步行为的基础上执行另一个异步行为 // 那我们应该怎么实现呢 function asynFn1(callback) { setTimeout(function() { console.log("complete 1"); callback(); }, 1000); } function asynFn2(callback) { setTimeout(function() { console.log("complete 2"); callback(); }, 1000); } function asynFn3(callback) { setTimeout(function() { console.log("complete 3"); callback(); }, 1000); } // 如果业务复杂的话,就不是这么简单的触发了 asynFn1(function() { asynFn2(); asynFn3(); });
功能是实现了,但实现的很繁琐,维护起来也很麻烦。如果我们用jQuery.deferred呢。
// 首先我们封装异步对象 function asynFn1() { var deferred = $.deferred(); // 当前还可以对延迟对象,比如说失败了应该怎么样 setTimeout(function() { console.log("complete 1"); // 触发真正的触发事件 deferred.resolve(); }, 1000); // promise 只提供done,fail,notity等添加触发后方法的接口, // 它不是提供直接触发事件的方法的 // 隐藏信息 return deferred.promise(); } // 业务复杂的情况也会是优雅的链式调用 asynFn1().done(asynFn2).done(asynFn3);
是不是瞬间高大上一点了呢。deferred用处很多,我一个小菜可能就想不了那么远了,还是看看我们的源码吧。
// 源码是通过extend扩展的,即直接把deferred对象放在jQuery的命名空间下 // 我们可以通过 jQuery.Deferred 或者 $.Deferred直接访问 jQuery.extend({ // 从参数命名来看,Deferred希望获得一个 function作参数 Deferred: function( func ) { // 定义一个元组 // 看这里有jQuery.Callbacks // 其实它的内部核心实现就是依赖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") ] ], // 初始状态为pending state = "pending", // 一个神奇的对象,他是用来暴露给外部的, // 主要负责注册监听器 promise = { //返回当前状态, 状态有 resolved, rejected, pending state: function() { return state; }, // 无论成功失败都需要执行的监听者 always: function() { deferred.done( arguments ).fail( arguments ); return this; }, // 一次性注册多个监听方法,顺序为 done, fail, progress /* 从浅显的角度看,类似于 function(d, f, g) { if (d) {this.done(d)}; if (f) {this.fail(d)}; if (g) {this.progress(d)}; } 但jQuery就实现的比较深了 */ // 最终返回新deferred的promise对象 then: function( /* fnDone, fnFail, fnProgress */ ) { // 首先保存参数 var fns = arguments; // 构建一个新的Deferred对象,并传递了一个函数做参数 // 这里在构建结束前,有这么多一段代码 /* // Call given func if any if ( func ) { // 以当前deferred调用并传递参数为当前deferred func.call( deferred, deferred ); } */ return jQuery.Deferred(function( newDefer ) { // 所以到这, newDefer指向的是新构建的deferred // each 遍历tuples jQuery.each( tuples, function( i, tuple ) { // 获取动作, reject, resolve, notify var action = tuple[ 0 ], // 获取刚刚保存的指定索引的fn fn = fns[ i ]; // deferred[ done | fail | progress ] for forwarding actions to newDefer // 将新的deferred的触发事件绑定在旧的deferred对象的想对应的监听器上 deferred[ tuple[1] ]( jQuery.isFunction( fn ) ? function() { var returned = fn.apply( this, arguments ); // 如果返回值是个deferred对象 if ( returned && jQuery.isFunction( returned.promise ) ) { // 返回延迟对象即不直接触发, // 而是讲newDefer的触发事件绑定在改延迟对象的监听器上 // 意思是等待该延迟对象触发再触发新延迟对象绑定的监听器 returned.promise() .done( newDefer.resolve ) .fail( newDefer.reject ) .progress( newDefer.notify ); } else { // 否则直接触发新的deferred的触发事件 // 注意这里用的是返回值作参数 // 通道实现 newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); } } : // 将新的deferred的触发事件绑定在旧的deferred对象的想对应的监听器上 newDefer[ action ] ); }); // 清空fns 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 ]; // done, fail, progress 监听器添加交给promise // promise[ done | fail | progress ] = list.add promise[ tuple[1] ] = list.add; // Handle state if ( stateString ) { // 如果有状态, // 则添加修改状态,禁用另个状态的功能, // 并锁住notify 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对象上 // 相当于这边是提供给服务端程序员 // 而promise是提供给客户端程序员 // deferred[ resolve | reject | notify ] = list.fire deferred[ tuple[0] ] = list.fire; 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; } });
还有一种异步情况:
// 假设我们有多个异步行为 // 而我们需要在这些异步行为都完成的情况下做一些额外的动作 // 那我们应该怎么实现呢 // 看例题吧 function asynFn1(callback) { setTimeout(function() { console.log("complete 1"); callback(); }, 1000); } function asynFn2(callback) { setTimeout(function() { console.log("complete 2"); callback(); }, 1000); } function asynFn3(callback) { setTimeout(function() { console.log("complete 3"); callback(); }, 1000); } // 那么怎么在监听到这三个异步方法都执行完的行为呢 // 貌似只能有到函数委托了 var proxy = function(callback) { var successCount = 0; return function() { successCount++; var args = [].slice.call(arguments); if (successCount === 3) { callback.apply(null, args); } }; } var pFn = proxy(function () { // 注册目标方法 console.log("i have nothing to wait"); }); // 将异步方法执行时绑定在代理后的目标方法 asynFn1(pFn); asynFn2(pFn); asynFn3(pFn); /* complete 1 complete 2 complete 3 i have nothing to wait */ var proxy2 = function(callback) { var successCount = 0; function add() { var asynFns = [].slice.call(arguments); successCount = asynFns.length; for (var fn in asynFns) { asynFns[fn](done); } } function done() { successCount--; if(!successCount) { callback(); } } return add; } var pFn2 = proxy2(function() { // 注册目标方法 console.log("i have nothing to wait 2"); }); // 将异步方法交于代理方法处理 pFn2(asynFn1, asynFn2, asynFn3); /* complete 1 complete 2 complete 3 i have nothing to wait 2 */
于是又出现了一个when方法
// Deferred helper when: function( subordinate /* , ..., subordinateN */ ) { var i = 0, resolveValues = core_slice.call( arguments ), length = resolveValues.length, // the count of uncompleted subordinates remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, // the master Deferred. If resolveValues consist of only a single Deferred, just use that. deferred = remaining === 1 ? subordinate : jQuery.Deferred(), // Update function for both resolve and progress values updateFunc = function( i, contexts, values ) { return function( value ) { contexts[ i ] = this; values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; if( values === progressValues ) { deferred.notifyWith( contexts, values ); } else if ( !( --remaining ) ) { deferred.resolveWith( contexts, values ); } }; }, progressValues, progressContexts, resolveContexts; // add listeners to Deferred subordinates; treat others as resolved if ( length > 1 ) { progressValues = new Array( length ); progressContexts = new Array( length ); resolveContexts = new Array( length ); for ( ; i < length; i++ ) { if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { resolveValues[ i ].promise() .done( updateFunc( i, resolveContexts, resolveValues ) ) .fail( deferred.reject ) .progress( updateFunc( i, progressContexts, progressValues ) ); } else { --remaining; } } } // if we're not waiting on anything, resolve the master if ( !remaining ) { deferred.resolveWith( resolveContexts, resolveValues ); } return deferred.promise(); }