简要: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

注:本人小菜一枚,若有不通之处,敬请指教


  


												

zepto源码研究 - deferred.js(jquery-deferred.js)的更多相关文章

  1. zepto源码研究 - zepto.js - 1

    简要:网上已经有很多人已经将zepto的源码研究得很细致了,但我还是想写下zepto源码系列,将别人的东西和自己的想法写下来以加深印象也是自娱自乐,文章中可能有许多错误,望有人不吝指出,烦请赐教. 首 ...

  2. zepto源码研究 - fx_methods.js

    简要:依赖fx.js,主要是针对show,hide,fadeIn,fadeOut的封装. 源码如下: // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zept ...

  3. zepto源码研究 - fx.js

    简要:zepto 提供了一个基础方法animate来方便我们运用css动画.主要针对transform,animate以及普通属性(例如left,right,height,width等等)的trans ...

  4. zepto源码研究 - ajax.js($.ajax具体流程分析)

    简要:$.ajax是zepto发送请求的核心方法,$.get,$.post,$.jsonp都是封装了$.ajax方法.$.ajax将jsonp与异步请求的代码格式统一起来,内部主要是先处理url,数据 ...

  5. zepto源码研究 - ajax.js($.ajaxJSONP 的分析)

    简要:jsonp是一种服务器和客户端信息传递方式,一般是利用script元素赋值src来发起请求.一般凡是带有src属性的元素发起的请求都是可以跨域的. 那么jsonp是如何获取服务器的数据的呢? j ...

  6. zepto源码研究 - ajax.js(请求过程中的各个事件分析)

    简要:ajax请求具有能够触发各类事件的功能,包括:触发全局事件,请求发送前事件,请求开始事件,请求结束事件等等,贯穿整个ajax请求过程,这是非常有用的,我们可以利用这些事件来做一些非常有意思的事情 ...

  7. zepto源码研究 - zepto.js - 6(模板方法)

    width  height  模板方法   读写width/height ['width', 'height'].forEach(function(dimension){ //将width,hegih ...

  8. zepto源码研究 - zepto.js - 5(dom属性管理)

    index: $.fn = {...... indexOf: emptyArray.indexOf,} index: function(element){ //这里的$(element)[0]是为了将 ...

  9. zepto源码研究 - callback.js

    简要:$.Callbacks是一个生成回调管家Callback的工厂,Callback提供一系列方法来管理一个回调列表($.Callbacks的一个私有变量list),包括添加回调函数, 删除回调函数 ...

随机推荐

  1. Js自动截取字符串长度,添加省略号“……”

    JavaScript字符串处理函数,根据定义的长度截取字符串,超出部分裁掉追加……,很多时候网页上显示的内容需要缩成“...”该方法用于处理字符串显示固定长度,超长部分用“...”代替: /**参数说 ...

  2. 转:VC中MessageBox的常见用法

    一.关于MessageBox       消息框是个很常用的控件,属性比较多,本文列出了它的一些常用方法,及指出了它的一些应用场合.       1.MessageBox("这是一个最简单的 ...

  3. Linux准备——安装Ubuntu系统

    今天对于我这个试图向安装Linux方向走的童鞋来说是个值得纪念的日子——我成功的安装上了Ubuntu操作系统. 整个的过程并不复杂,主要是看了网上Linux社区的一个安装教程.开始,我还在XP下安装, ...

  4. Java比较两个日期的大小

    public static String getComparedSBQDate(String yxqq,String starttime){ String str = starttime; Simpl ...

  5. 解决TextView与RadioGroup不对齐的问题

    TextView和RadioGroup是在同一个LinearLayout中的,控件摆放方式是android:orientation="horizontal",虽然三个控件是水平摆放 ...

  6. nosql newsql

    http://www.cnblogs.com/end/archive/2011/10/19/2217244.html http://www.csdn.net/article/2011-09-26/30 ...

  7. Thread 1 cannot allocate new log的问题分析

    http://blog.csdn.net/zonelan/article/details/7613519 http://leoguan.blog.51cto.com/816378/584494 htt ...

  8. BZOJ 4407 于神之怒加强版

    http://www.lydsy.com/JudgeOnline/problem.php?id=4407 题意: 给下N,M,K.求 思路:  来自:http://blog.csdn.net/ws_y ...

  9. 创建.NET应用程序所经历的步骤

    1.使用某种.NET兼容语言(如C#)编写应用程序.2.把代码编译为(CIL),存储在程序集中.3.在执行代码时(如果这是一个可执行文件,就自动运行,或者在其他代码使用它时运行),首先必须使用JIT( ...

  10. altium designer不经过原理图直接在空白pcb上加封装然后画线

    如果是复杂点的PCB,建议还是画下SCH,如果PCB只有几个元件,那么可以用这种方法,想不画原理图,直接进行布线,往往是很多初学者最想知道的,但是这也一定不是初学者能学到的.因为你买的书,都是按画PC ...