zepto源码研究 - deferred.js(jquery-deferred.js)
简要:zepto的deferred.js 并不遵守promise/A+ 规范,而在jquery v3.0.0中的defer在一定程度上实现了promise/A+ ,因此本文主要研究jquery v3.0.0中的defer。
首先 在上源码前,本人觉得有必要认识一下promise/A+ 规范:https://segmentfault.com/a/1190000002452115
接下来上源码:
define( [ "./core", "./var/slice", "./callbacks" ], function( jQuery, slice ) { "use strict"; function Identity( v ) { return v; } function Thrower( ex ) { throw ex; } function adoptValue( value, resolve, reject ) { var method; try { // Check for promise aspect first to privilege synchronous behavior if ( value && jQuery.isFunction( ( method = value.promise ) ) ) { method.call( value ).done( resolve ).fail( reject ); // Other thenables } else if ( value && jQuery.isFunction( ( method = value.then ) ) ) { method.call( value, resolve, reject ); // Other non-thenables } else { // Support: Android 4.0 only // Strict mode functions invoked without .call/.apply get global-object context // 假设value是常量,resolve会立刻调用,并且传入参数为value resolve.call( undefined, value ); } // For Promises/A+, convert exceptions into rejections // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in // Deferred#then to conditionally suppress rejection. } catch ( value ) { // Support: Android 4.0 only // Strict mode functions invoked without .call/.apply get global-object context // 这里执行master.reject(value) reject.call( undefined, value ); } } jQuery.extend( { Deferred: function( func ) { //元组:描述状态、状态切换方法名、对应状态执行方法名、回调列表的关系 //tuple引自C++/python,和list的区别是,它不可改变 ,用来存储常量集 var tuples = [ // action, add listener, callbacks, // ... .then handlers, argument index, [final state] [ "notify", "progress", jQuery.Callbacks( "memory" ), jQuery.Callbacks( "memory" ), 2 ], [ "resolve", "done", jQuery.Callbacks( "once memory" ), jQuery.Callbacks( "once memory" ), 0, "resolved" ], [ "reject", "fail", jQuery.Callbacks( "once memory" ), jQuery.Callbacks( "once memory" ), 1, "rejected" ] ], state = "pending", //Promise初始状态 //promise对象,promise和deferred的区别是: /*promise只包含执行阶段的方法always(),then(),done(),fail(),progress()及辅助方法state()、promise()等。 deferred则在继承promise的基础上,增加切换状态的方法,resolve()/resolveWith(),reject()/rejectWith(),notify()/notifyWith()*/ //所以称promise是deferred的只读副本 promise = { /** * 返回状态 * @returns {string} */ state: function() { return state; }, /** * 成功/失败状态的 回调调用 * @returns {*} */ always: function() { deferred.done( arguments ).fail( arguments ); return this; }, // TODO 待解释 "catch": function( fn ) { return promise.then( null, fn ); }, /** * * @returns promise对象 */ // Keep pipe for back-compat pipe: function( /* fnDone, fnFail, fnProgress */ ) { var fns = arguments; //注意,这无论如何都会返回一个新的Deferred只读副本, //所以正常为一个deferred添加成功,失败,千万不要用pipe,用done,fail return jQuery.Deferred( function( newDefer ) { jQuery.each( tuples, function( i, tuple ) { // Map tuples (progress, done, fail) to arguments (done, fail, progress) // 根据tuple,从fns里取出对应的fn var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; // deferred.progress(function() { bind to newDefer or newDefer.notify }) // deferred.done(function() { bind to newDefer or newDefer.resolve }) // deferred.fail(function() { bind to newDefer or newDefer.reject }) //注册fn的包装函数 deferred[ tuple[ 1 ] ]( function() { //直接执行新添加的回调 fnDone fnFailed fnProgress var returned = fn && fn.apply( this, arguments ); //返回结果是promise对象 if ( returned && jQuery.isFunction( returned.promise ) ) { //转向fnDone fnFailed fnProgress返回的promise对象 //注意,这里是两个promise对象的数据交流 //新deferrred对象切换为对应的成功/失败/通知状态,传递的参数为 returned.promise() 给予的参数值 returned.promise() .progress( newDefer.notify ) .done( newDefer.resolve ) .fail( newDefer.reject ); } else { //新deferrred对象切换为对应的成功/失败/通知状态 newDefer[ tuple[ 0 ] + "With" ]( this, fn ? [ returned ] : arguments ); } } ); } ); fns = null; } ).promise(); }, then: function( onFulfilled, onRejected, onProgress ) { var maxDepth = 0; //depth == 0; //special == newDefer.notifyWith,用来判断是否为pending状态触发 //deferred == jQuery.Deferred() 返回的新的defer对象即then()返回的对象即newDefer //handler == [onFulfilled,onRejected,onProgress]||Identity,Identity为简单返回形参的fn, //在resolve返回的fn中handle以deferred为上下文执行 function resolve( depth, deferred, handler, special ) { //这个fn会在then的调用者对应的回调列表中,是handler的包装函数,在此称之为wrappeHandler; return function() { //这个this是不定的,比如 fn.call(obj,[args]) var that = this, args = arguments, //TODO 这是什么功能? mightThrow = function() { var returned, then; // Support: Promises/A+ section 2.3.3.3.3 // https://promisesaplus.com/#point-59 // Ignore double-resolution attempts // 一般是0和0,1和1,有种情况是0和1 // defer.then(sfn,rfn,nfn);nfn先执行并返回promise,该promise会then(fn1), // 注:此fn1是由resolve(depth = 0)生成,因此fn1内部执行时depth == 0 // 接下来执行sfn,此时返回promise1,maxDepth++, // 如果接下来promise被resolve了,便会执行fn1,便会出现上述情况 if ( depth < maxDepth ) { return; } //传入的handler以当前上下文和参数执行 returned = handler.apply( that, args ); // Support: Promises/A+ section 2.3.1 // https://promisesaplus.com/#point-48 // newPromise1 = defer.then(function(){return newPromise}); // newPromise在resolve时执行回调函数fn1,而fn1执行newPromise1的回调函数, // 若newPromise1 === newPromise,则会出现死循环 if ( returned === deferred.promise() ) { throw new TypeError( "Thenable self-resolution" ); } // Support: Promises/A+ sections 2.3.3.1, 3.5 // https://promisesaplus.com/#point-54 // https://promisesaplus.com/#point-75 // Retrieve `then` only once // 如果有returned.then,则returned为promise then = returned && // Support: Promises/A+ section 2.3.4 // https://promisesaplus.com/#point-64 // Only check objects and functions for thenability ( typeof returned === "object" || typeof returned === "function" ) && returned.then; // Handle a returned thenable if ( jQuery.isFunction( then ) ) { // Special processors (notify) just wait for resolution // special可判断此wrappeHandler在pending列表中还是在resolver或reject中 if ( special ) { then.call( returned, resolve( maxDepth, deferred, Identity, special ), resolve( maxDepth, deferred, Thrower, special ) ); // Normal processors (resolve) also hook into progress } else { //如果这个包装函数wrappeHandler是在resolve列表或reject列表中 //newDefer = defer.then(function(){return promise}); //defer在resolve时候执行function(){return promise}的包装函数,在此包装函数中则会执行到此 //即要执行到此,则必须满足 1:此包装函数对应的defer resolve and reject,2:hander 返回promise // ...and disregard older resolution values // notify里面返回的promise在resolve后的参数可能会传递给第2个then去执行 // 如果在这之前resolve已经将参数传递给了第2个then,这里要防止老数据 maxDepth++; /* * returned.then(resolve( maxDepth, deferred, Identity, special )) * deferred:下文中的newDefer,作用是Identity()执行后,newDefer.resolveWidth * */ then.call( returned, resolve( maxDepth, deferred, Identity, special ), resolve( maxDepth, deferred, Thrower, special ), resolve( maxDepth, deferred, Identity, deferred.notifyWith ) ); } // Handle all other returned values } else { // Only substitute handlers pass on context // and multiple values (non-spec behavior) /* * newDefer = defer.then(fn1).then(fn2); * defer注册fn1,将封装了fn1的包装函数bfn1加入到defer的回调列表中,newDefer注册fn2,同理有bfn2 * 这里是 handler 返回的是普通对象,则newDefer立即resolve的即立即执行fn2 * 如果走defer走resolve流程时,此时fn1 === handler的则newDefer.resolve(fn1的that,fn1的args); * 如果fn1返回的returned 走resolve流程,此时handler === identity,则newDefer.resolve(undefined,identity的return); * */ if ( handler !== Identity ) { that = undefined; args = [ returned ]; } // Process the value(s) // Default process is resolve // 无论then里面的函数返回的promise是notify,resolve,reject,最终都会执行resolveWith ( special || deferred.resolveWith )( that, args ); } }, // Only normal processors (resolve) catch and reject exceptions // 这里是对异常的处理?? process = special ? mightThrow : function() { try { mightThrow(); } catch ( e ) { /*jQuery.Deferred.exceptionHook = function( error, stack ) { // Support: IE 8 - 9 only // Console exists when dev tools are open, which can happen at any time if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); } };*/ if ( jQuery.Deferred.exceptionHook ) { jQuery.Deferred.exceptionHook( e, process.stackTrace ); } // Support: Promises/A+ section 2.3.3.3.4.1 // https://promisesaplus.com/#point-61 // Ignore post-resolution exceptions // 正常的注册结构出现异常会走下面流程 // TODO 暂不清楚 depth + 1 < maxDepth 的情况 if ( depth + 1 >= maxDepth ) { // Only substitute handlers pass on context // and multiple values (non-spec behavior) if ( handler !== Thrower ) { that = undefined; args = [ e ]; } //正常情况下第一个then的resolve和reject出现异常,会导致第二个then里面的reject执行 deferred.rejectWith( that, args ); } } }; // Support: Promises/A+ section 2.3.3.3.1 // https://promisesaplus.com/#point-57 // Re-resolve promises immediately to dodge false rejection from // subsequent errors if ( depth ) { process(); } else { // 最开始的触发 // Call an optional hook to record the stack, in case of exception // since it's otherwise lost when execution goes async // TODO 不太明白 if ( jQuery.Deferred.getStackHook ) { process.stackTrace = jQuery.Deferred.getStackHook(); } window.setTimeout( process ); } }; } /* * defer.then(fn) --> * 创建newDefer --> * defer关联的回调列表(下文中的tuples[ * ][ 3 ])增加一个fn的包装函数(由resolve生成), * 这个包装函数执行fn,并对其返回值和newDefer做出相应处理 --> * 返回newDefer * */ return jQuery.Deferred( function( newDefer ) { // progress_handlers.add( ... ) tuples[ 0 ][ 3 ].add( resolve( 0, newDefer, jQuery.isFunction( onProgress ) ? onProgress : Identity, newDefer.notifyWith ) ); // fulfilled_handlers.add( ... ) tuples[ 1 ][ 3 ].add( resolve( 0, newDefer, jQuery.isFunction( onFulfilled ) ? onFulfilled : Identity ) ); // rejected_handlers.add( ... ) tuples[ 2 ][ 3 ].add( resolve( 0, newDefer, jQuery.isFunction( onRejected ) ? onRejected : Thrower ) ); } ).promise(); //返回defer的只读版本promise }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object /** * 返回obj的promise对象 * @param obj * @returns {*} */ promise: function( obj ) { return obj != null ? jQuery.extend( obj, promise ) : promise; } }, //内部封装deferred对象 deferred = {}; // Add list-specific methods //给deferred添加切换状态方法 jQuery.each( tuples, function( i, tuple ) { var list = tuple[ 2 ], stateString = tuple[ 5 ]; // promise.progress = list.add // promise.done = list.add // promise.fail = list.add //扩展promise的done、fail、progress为Callback的add方法,使其成为回调列表 //简单写法: promise['done'] = jQuery.Callbacks( "once memory" ).add // promise['fail'] = jQuery.Callbacks( "once memory" ).add promise['progress'] = jQuery.Callbacks( "memory" ).add promise[ tuple[ 1 ] ] = list.add; // Handle state //切换的状态是resolve成功/reject失败 //添加首组方法做预处理,修改state的值,使成功或失败互斥,锁定progress回调列表, if ( stateString ) { /* if (stateString) { list.add(function(){ state = stateString //i^1 ^异或运算符 0^1=1 1^1=0,成功或失败回调互斥,调用一方,禁用另一方 }, tuples[i^1][2].disable, tuples[2][2].lock) } */ list.add( function() { // state = "resolved" (i.e., fulfilled) // state = "rejected" state = stateString; }, // rejected_callbacks.disable // fulfilled_callbacks.disable tuples[ 3 - i ][ 2 ].disable, // progress_callbacks.lock tuples[ 0 ][ 2 ].lock ); } // progress_handlers.fire // fulfilled_handlers.fire // rejected_handlers.fire list.add( tuple[ 3 ].fire ); // deferred.notify = function() { deferred.notifyWith(...) } // deferred.resolve = function() { deferred.resolveWith(...) } // deferred.reject = function() { deferred.rejectWith(...) } //添加切换状态方法 resolve()/resolveWith(),reject()/rejectWith(),notify()/notifyWith() deferred[ tuple[ 0 ] ] = function() { deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); return this; }; // deferred.notifyWith = list.fireWith // deferred.resolveWith = list.fireWith // deferred.rejectWith = list.fireWith deferred[ tuple[ 0 ] + "With" ] = list.fireWith; } ); // Make the deferred a promise //deferred继承promise的执行方法 promise.promise( deferred ); // Call given func if any //传递了参数func,执行 if ( func ) { func.call( deferred, deferred ); } // All done! //返回deferred对象 return deferred; }, // Deferred helper /** * * 主要用于多异步队列处理。 多异步队列都成功,执行成功方法,一个失败,执行失败方法 也可以传非异步队列对象 * @param sub * @returns {*} */ when: function( singleValue ) { var // count of uncompleted subordinates remaining = arguments.length, // count of unprocessed arguments i = remaining, // subordinate fulfillment data resolveContexts = Array( i ), resolveValues = slice.call( arguments ), //队列数组 ,未传参数是[],slice能将对象转化为数组 // the master Deferred // 这是主分支 .when().then(),master决定.then()的执行 master = jQuery.Deferred(), // subordinate callback factory // .when()参数中每一个value被resolve后调用下面的返回函数 // 1:将每一个调用者和调用参数存在数组里,2: 最后以数组作为参数,由master.resolve updateFunc = function( i ) { return function( value ) { resolveContexts[ i ] = this; // updateFunc()(v1,v2,v3),resolveValues[ i ] = [v1,v2,v3],若只有一个参数, // 则resolveValues[ i ] = value resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; if ( !( --remaining ) ) { //如果这是最后一个resolveValues被解决 master.resolveWith( resolveContexts, resolveValues ); } }; }; // Single- and empty arguments are adopted like Promise.resolve if ( remaining <= 1 ) { // 将第二个和第三个参数注册到第一个参数里面去 // 如果singleValue是常量,则立刻执行master.resolve,下面的判断不会执行 adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject ); // Use .then() to unwrap secondary thenables (cf. gh-3000) // if ( master.state() === "pending" || jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { return master.then(); } } // Multiple arguments are aggregated like Promise.all array elements // 循环为resolveValues[i] 注册updateFunc()方法 --> 判断计数到最后一个则执行 master.resolve // resolveValues[i].reject-->list.add(master.reject); while ( i-- ) { // 当resolveValues[ i ]为常量时,会立刻执行updateFunc( i ), // 如果所有的都为常量,则 执行master.resolve(resolveValues) adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); } return master.promise(); } } ); return jQuery; } );
以上内容主要是针对then,pipe,when 做了更新与修改,
pipe:jquery.Deferred(fn)里面会先新建一个newDefer,然后传入newDefer作为参数并执行fn,fn会将pipe里的参数依次加入到回调列表中,并且判断参数返回值是否为promise,若果是,则将newDefer的状态转化器注入到promise的回调列表中,否则,直接让newDefer发生相应的状态转化。
测试代码如下:
var defer = $.Deferred(); defer.pipe(function(data){ console.log(data); var defer1 = $.Deferred(); setTimeout(function () { defer1.resolve("second pipe"); },1000); return defer1 }).pipe(function (data) { console.log(data); }); defer.resolve("first pipe"); 结果: first pipe second pipe
then:主要流程如下:
function then(fnDone,fnFail,fnPro){ var maxDepth = 0; //声明调用深度,这里的链式调用实为递归调用 function resolve().... //resolve封装了then的参数 var defer = jQuery.Deferred(function (newDefer) { Tuples.add(resolve(fnDone),resolve(fnFail),resolve(fnPro)); }) }
这里主要流程在resolve函数里面,resolve返回一个封装函数fn,这个fn的主要功能是管理和执行resovle中的handle参数,mightThrow 方法主要实现基本的promise功能
如果fnDone和fnFail出现了异常,则捕获异常,并newDefer.reject();
如下例子:
var defer = $.Deferred(); defer.then(function () { throw new Error("resolve error"); }).then(function () { console.log("second then resolve"); },function (data) { console.log("second then reject"); console.log(data); }); defer.resolve(); 结果: second then reject Error: resolve error at file:///defer.html:10:19 at jQuery.extend.Deferred.promise.then.mightThrow (file:///jquery-3.0.0.js:3507:29) at jQuery.extend.Deferred.promise.then.process (file:///jquery-3.0.0.js:3576:12)
mightThrow方法 首先执行 returned = handler.apply(that,args); 如果returned的类型是promise,则将newDefer的状态转换交给returned的回调列表进行管理,若为常量,则直接调用newDefer.resolve(); 从pending状态到resolve状态其中的流程大多是不可控的,为避免pending的数据影响resolve ,于是用depth来区分。
defer.when: 传一个或一组参数,可以是常量也可以是promise,这里有两个地方我觉得是非常好的,第一个是把when中每个参数完成后的计数操作提取出来形成一个函数
updateFunc(i),第二个是adoptValue ,将master的状态变化注入到每个参数的回调列表中由其统一管理。
如下例子:
$.when("no-promise").then(function (data) { console.log(data + "Execution without delay!"); }); 结果: no-promiseExecution without delay! var defer1 = $.Deferred(); var defer2 = $.Deferred(); var defer3 = $.Deferred(); $.when(defer1,defer2,defer3).then(function (d1,d2,d3) { console.log(d1); console.log(d2); console.log(d3); }); setTimeout(function () { defer1.resolve("defer1"); },1000); setTimeout(function () { defer2.resolve("defer2"); },2000); setTimeout(function () { defer3.resolve("defer3"); },3000); 结果: defer1 defer2 defer3
注:本人小菜一枚,若有不通之处,敬请指教