深入理解jQuery中的Deferred

引入

  1  在开发的过程中,我们经常遇到某些耗时很长的javascript操作,并且伴随着大量的异步。

  2  比如我们有一个ajax的操作,这个ajax从发出请求到接收响应需要5秒,在这5秒内我们可以运行其他代码段,当响应到达后,我们需要判断响应的结果(无非就是成功或者失败),并根据不同的结果  添加回调函数

  3  为了有效的简洁的添加回调函数jQuery引入了Callbacks。

  4  而为了方便的 根据不同的结果(或者根据各种跟结果有关的逻辑,比如不管是成功或者失败) 添加回调函数,jQuery引入了Deferred。

 $.ajax("test.html").done(function(){ alert("success"); }).fail(function(){ alert("error"); });

  5  因而Deferred与Callbacks是密不可分的,事实上,Callbacks也是从Deferred中分离出去的

回顾Callbacks

  1  Callbacks大体架构

  2 Callbacks源码分析:

  define(["./core","./var/rnotwhite"
], function( jQuery, rnotwhite ) {// String to Object options format cache
var optionsCache = {};// Convert String-formatted options into Object-formatted ones and store in cache
/*
如果: var a = $.Callback('once memory')
则 optionsCache中会有这么一项:"once memory":{memory:true,once:true}
*/
function createOptions( options ) {var object = optionsCache[ options ] = {};jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {object[ flag ] = true;});return object;
}/** Create a callback list using the following parameters:**    options: an optional list of space-separated options that will change how*            the callback list behaves or a more traditional option object** By default a callback list will act like an event callback list and can be* "fired" multiple times.** Possible options:**    once:            will ensure the callback list can only be fired once (like a Deferred)**    memory:            will keep track of previous values and will call any callback added*                    after the list has been fired right away with the latest "memorized"*                    values (like a Deferred)**    unique:            will ensure a callback can only be added once (no duplicate in the list)**    stopOnFalse:    interrupt callings when a callback returns false**/
jQuery.Callbacks = function( options ) {// Convert options from String-formatted to Object-formatted if needed// (we check in cache first)options = typeof options === "string" ?( optionsCache[ options ] || createOptions( options ) ) :jQuery.extend( {}, options );var // Last fire value (for non-forgettable lists)memory,// Flag to know if list was already fired  list是否已经被fire函数调用过fired,// Flag to know if list is currently firing  当前是否正在调用fire函数firing,// First callback to fire (used internally by add and fireWith)  第一个被执行的回调函数在list的位置firingStart,// End of the loop when firing   fire函数要运行的回调函数的个数firingLength,// Index of currently firing callback (modified by remove if needed)  当前正在执行的回调函数的索引firingIndex,//回调函数数组list = [],// Stack of fire calls for repeatable lists  可重复的回调函数栈。我们可能会短时间内执行多次fire(),若当前的fire()正在迭代执行回调函数,而紧接着又执行了一次fire()时,会将下一次的fire()参数等保存至stack中,等待当前的fire()执行完成后,将stack中的fire()进行执行stack = !options.once && [],// Fire callbacksfire = function( data ) {// data[0] 是一个对象,data[1]则是回调函数的参数memory = options.memory && data;  // 很精妙,仔细体会一下这句代码,如果调用Calbacks时传入了memory,则memory = data,否则memory = falsefired = true; // 在调用本函数时,将fired状态进行修改firingIndex = firingStart || 0;firingStart = 0;firingLength = list.length;firing = true; // 迭代回调函数之前,将firing状态进行修改for ( ; list && firingIndex < firingLength; firingIndex++ ) {if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false &&options.stopOnFalse ) { // 运行回调函数的同时,检测回调函数是否返回false,若返回false,且调用Callbacks时传入stopOnFalse参数,则终止迭代memory = false; // To prevent further calls using add 既然终止迭代了,那么之后添加的回调函数都不应该被调用,将memory设置为falsebreak;}}firing = false;  // 迭代回调函数完成后,将firing状态进行修改if ( list ) {if ( stack ) {  // 没有使用once参数if ( stack.length ) {fire( stack.shift() );}} else if ( memory ) { // 使用了once memory参数,则在迭代完回调函数之后清空listlist = [];} else { // 其他self.disable();}}},// Actual Callbacks objectself = {// 将一个新的回调函数添加至listadd: function() {if ( list ) {// First, we save the current length  首先,我们将当前的长度进行保存var start = list.length;(function add( args ) {  // 自执行函数jQuery.each( args, function( _, arg ) {var type = jQuery.type( arg );if ( type === "function" ) {if ( !options.unique || !self.has( arg ) ) {list.push( arg ); // 若参数中的元素为函数且(无unique参数或者list中没有该函数),则将该函数添加至list末尾}} else if ( arg && arg.length && type !== "string" ) { //  arg的长度不为0且每项的类型不为字符串,也就是args为这种情况:[[fun1,fun2...],[fun3,fun4]](不仅限于这种情况)// Inspect recursivelyadd( arg );}});})( arguments );// Do we need to add the callbacks to the// current firing batch?// 当Callback中的firingLength变为 动态的! 也就是:只要我们向list中添加了一个新的回调函数,即使在fire()运行过程中,改变也能立即体现出来if ( firing ) {firingLength = list.length;// With memory, if we're not firing then// we should call right away} else if ( memory ) { // 如果当前没有执行回调函数,且存在memory参数,则执行新添加的回调函数firingStart = start;fire( memory );}}return this;},// Remove a callback from the list 将一个回调函数从list中移除remove: function() {if ( list ) {jQuery.each( arguments, function( _, arg ) {var index;while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {list.splice( index, 1 );// Handle firing indexesif ( firing ) {if ( index <= firingLength ) {firingLength--;}if ( index <= firingIndex ) {firingIndex--;}}}});}return this;},// Check if a given callback is in the list.// If no argument is given, return whether or not list has callbacks attached.has: function( fn ) {return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );},// Remove all callbacks from the list 清空数组empty: function() {list = [];firingLength = 0;return this;},// Have the list do nothing anymore  使用了这个方法,则意味着该回调对象失效了。disable: function() {list = stack = memory = undefined;return this;},// Is it disabled?disabled: function() {return !list;},// Lock the list in its current state 给数组上锁lock: function() {stack = undefined;if ( !memory ) {self.disable();}return this;},// Is it locked?locked: function() {return !stack;},// Call all callbacks with the given context and arguments// 使用传入的context作为当前函数的执行上下文fireWith: function( context, args ) {if ( list && ( !fired || stack ) ) {args = args || [];args = [ context, args.slice ? args.slice() : args ];if ( firing ) {stack.push( args ); // 如果当前正在迭代执行回调函数,则将新的fire参数推入stack中} else {fire( args );}}return this;},// Call all the callbacks with the given argumentsfire: function() {self.fireWith( this, arguments );return this;},// To know if the callbacks have already been called at least once// 用来确定当前callback对象是否被fire()过fired: function() {return !!fired;}};return self;
};return jQuery;
});

  另外要注意下面两个参数:

    once:如果创建Callbacks时加入该参数,则运行数组中的所有回调函数之后,也就是fire()之后,会清空数组。

    memory:会保存上一次运行fire(args)时的参数args,每当添加一个新的回调函数到数组中,会立即使用args作为参数调用新加的函数一次

Deferred讲解:

Deferred大体架构:

1  先来看一看tuple数组

var tuples = [// action, add listener, listener list, final state[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],// 解决 操作成功  Callbacks对象 最终状态为解决[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], // 拒绝  操作失败 Callbacks对象 最终状态为拒绝[ "notify", "progress", jQuery.Callbacks("memory") ]  // 通知  操作进行中 Callbacks对象 最终状态无(操作进行中的最终状态就是操作完成,完成无非就是转变为上面两种 成功或者失败)]

 jQuery的设计理念是这样的:

  1  deferred对象有三种执行状态----完成 失败 进行中。

  2  每种状态对应一个Callbacks实例

  3  如果执行状态是"完成"(resolved),deferred对象立刻调用done()方法指定的回调函数(也就是执行已完成状态对应的Callbacks实例的fire方法);如果执行状态是"失败",调用fail()方法指定的回调函数;如果执行状态是"进行中",则继续等待,或者调用progress()方法指定的回调函数。

2  promise对象和deferred对象

一个是deferred外部接口对象,一个是内部promise对象。

promise对象是一个受限的对象, 这就是所谓的受限制的deferred对象,因为相比deferred对象, promise对象没有resolve(With), reject(With), notify(With)这些能改变deferred对象状态并且执行callbacklist的方法了,只能是then、done、fali等方法。

3  done  fail  progress 方法 与 resolve  reject  notify方法

  在上图中我们已经说明了前三个方法就是Callbacks中的add方法。后三个调用了Callbacks中的fireWith()方法。

  所以,我们可以总结出:

    1  三种状态各对应一个Callbacks实例

    2  使用done 或fail 或progress时,实际上就是往各自对应的Callbacks实例中的list数组添加回调函数

    3  使用resolve 或reject 或notify时,则就是运行各自对应Callbacks实例中的list数组中的回调函数

4  then方法

  then方法创建了一个新的promise对象,then就是pipe,我们可以想象是一个管道。管道就是能 ‘承上启下’(更贴切的来说,在Deferred中的then只做了承上,仅仅是个人观点)

var a = $.Deferred();
a.then(function(val){console.log(val); // 2return val * 2
}).then(function(val){console.log(val); // 4
});
a.resolve(2)

如案例所示,下一个回调对象都能取到上一个回调对象的值,这样一直可以叠加往后传递。

关于then可能看了非常迷糊,不要紧,上面说的是then的高级特性,平时我们基本不怎么使用的。

平时我们大部分是使用then来代替done以及fail:

$.when($.ajax( "test.php" )).then(successFunction, failureFunction );

deferred源码分析

    define(["./core","./var/slice","./callbacks"
], function( jQuery, slice ) {jQuery.extend({Deferred: function( func ) {// 创建一个tuples数组,一个promise对象,一个deferred对象,一个state变量var tuples = [// action, add listener, listener list, final state[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],// 解决 操作成功  Callbacks对象 最终状态为解决[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], // 拒绝  操作失败 Callbacks对象 最终状态为拒绝[ "notify", "progress", jQuery.Callbacks("memory") ]  // 通知  操作进行中 Callbacks对象 最终状态无(操作进行中的最终状态就是操作完成,完成无非就是转变为上面两种 成功或者失败)],state = "pending",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 fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];// deferred[ done | fail | progress ] for forwarding actions to newDeferdeferred[ tuple[1] ](function() {  // 注意,这里的deferred指的不是新Deferred对象中的deferred(也就是不是指的newDefer)var returned = fn && fn.apply( this, arguments );// 若returned是一个deferred对象,则为returned添加一个回调函数,这个回调函数运行后使newDefer能够接收到returned的fire()参数if ( returned && jQuery.isFunction( returned.promise ) ) {returned.promise().done( newDefer.resolve ).fail( newDefer.reject ).progress( newDefer.notify );} else {newDefer[ tuple[ 0 ] + "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 objectpromise: function( obj ) { // 若obj非null,则将obj与promise对象结合并返回,否则返回promise对象return obj != null ? jQuery.extend( obj, promise ) : promise;}},deferred = {};// Keep pipe for back-compat  和之前的版本兼容promise.pipe = promise.then;// Add list-specific methodsjQuery.each( tuples, function( i, tuple ) {var list = tuple[ 2 ], // 这是一个Callbacks 对象stateString = tuple[ 3 ]; // 用字符串表示的状态// promise[ done | fail | progress ] = list.addpromise[ tuple[1] ] = list.add;  // Callbacks对象的add函数// Handle state  处理状态if ( stateString ) {list.add(function() {// state = [ resolved | rejected ]state = stateString;  // Callbacks对象中的回调函数列表第一项:改变状态// [ reject_list | resolve_list ].disable; progress_list.lock},tuples[ i ^ 1 ][ 2 ].disable,   //  因为reject,resolve是对立的,当行为为reject,那么resolve的Callbacks就无用了,将其回调函数列表清空即可tuples[ 2 ][ 2 ].lock ); // 当行为为reject或者resolve时,即"结果已确定",那么就不允许再调用 "操作进行中" 的Callbacks对象的fire()了}// 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合并至deferredpromise.promise( deferred );// Call given func if any  在初始化完deferred对象后,会立即运行func函数,并把deferred作为第一个参数传入if ( func ) {func.call( deferred, deferred );}// All done!return deferred;},// Deferred helperwhen: function( subordinate /* , ..., subordinateN */ ) {/* 大体思路:*    若参数(参数必需是deferred对象)只有一个,则返回这个参数的promise对象*   若参数有多个,则生成一个新的deferred对象,并返回deferred对象的promise对象*      当所有参数的状态为完成时,使新deferred对象的状态变为完成,*      若有一个参数的状态为失败,则使新deferred对象的状态变为失败** */var i = 0,resolveValues = slice.call( arguments ),//slice是数组的slice方法,一般情况下:resolveValues就是一个由deferred组成的数组length = resolveValues.length,// the count of uncompleted subordinates 没有运行完成的deferred对象的数量// 如果length长度不为1或者subordinate是一个deferred对象则,remaining=length,否则remaining为0remaining = length !== 1 ||( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,//运算符优先级: 逻辑与逻辑或 优先级高于 ? :// the master Deferred.// If resolveValues consist of only a single Deferred, just use that.// 如果remaining为1,则使用subordinate(这是一个deferred对象)即可,否则创建一个新的deferred对象deferred = remaining === 1 ? subordinate : jQuery.Deferred(),// Update function for both resolve and progress valuesupdateFunc = function( i, contexts, values ) {return function( value ) {contexts[ i ] = this;values[ i ] = arguments.length > 1 ? 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 resolvedif ( length > 1 ) {progressValues = new Array( length );progressContexts = new Array( length );resolveContexts = new Array( length );// 迭代resolveValues中的每一个deferred对象,为其添加不同状态下的回调函数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 masterif ( !remaining ) {deferred.resolveWith( resolveContexts, resolveValues );}return deferred.promise();}
});return jQuery;
});

posted on 2017-05-05 22:57 玲儿灵 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/jymz/p/6815254.html

深入理解jQuery中的Deferred相关推荐

  1. jQuery中的Deferred详解和使用

    首先,为什么要使用Deferred? 先来看一段AJAX的代码: var data;$.get('api/data', function(resp) {data = resp.data;});doSo ...

  2. 深入理解jQuery中$.get、$.post、$.getJSON和$.ajax的用法

    当我们用javascript写ajax程序写得很"开心"的时候,突然有人告诉你有一种东西叫jquery,它会告诉你不直接和HttpRequest是多么的快乐,同时你再也不需要再烦恼 ...

  3. c语言中extend用法,深入理解jquery中extend的实现

    Jquery的扩展方法extend是我们在写插件的过程中常用的方法,该方法有一些重载原型,下面来看看详细的介绍吧. 通常我们使用jquery的extend时,大都是为了实现默认字段的覆盖,即若传入某个 ...

  4. jQuery 中的 Deferred 和 Promises

    看前首先了解:Promises/A规范,具体可以看这里,http://bijian1013.iteye.com/blog/2392429 由于jQuery版本问题对Deferred对象的实现有所不同, ...

  5. ajax请求中带判断语句例子,jQuery中借助deferred来请求及判断AJAX加载的实例讲解...

    ajax请求异步队列加载我们在开发程序的时候通常会碰到使用ajax加载数据显示到列表的情况.ajax默认使用异步加载(async:true).为什么不使用同步呢,因为ajax同步加载会UI渲染线程阻塞 ...

  6. 理解JQuery中的data()使用方法

    在前端我们经常会做的操作就是做数据状态的判断和数据处理.提交,经常会操作dom,也会保存一个全局的数据处理.这样做是可以实现很多功能,但是缺点就是过多操作dom会浪费性能,全局数据保存多了有时候真的会 ...

  7. 深度理解Jquery 中 scrollTop() 方法

    这是工作遇到scrollTop() 方法.为了强化自己,把它记录在博客园当中. 下面就开始scrollTop 用法讲解: scrollTop() 定义和用法 scrollTop() 方法设置或返回被选 ...

  8. ajax在jQuery的应用,Ajax在jQuery中的应用($.ajax()方法)

    Ajax() 方法 $.ajax() 中的参数及使用方法 在jQuery中,$.ajax() 是最底层的方法,也是功能最强的方法.$.get().$.post().$.getScript().getJ ...

  9. jQuery中deferred对象详解

    最近看看构建工具gulp,当看到promise时突然有点陌生,于是乎意外找到了大牛阮一峰的一篇文章,写的非常的通俗易懂,对延迟对象进行了很好的讲解,虽然写的比较早但是道理都一样. 以下为主要内容: 一 ...

最新文章

  1. 《JavaScript高效图形编程(修订版)》——6.10 用画布sprites取代DHTMLsprite
  2. MySQL - 锁机制初探
  3. SPC5Studio 入门
  4. bootstrap时间控件
  5. Java:使用终止信号确定数据导入的状态
  6. HTML滚动条S默认最小值,css修改滚动条默认样式
  7. struts2中的session使用
  8. BuzzFeed如何从Perl单体应用迁移到Go和Python微服务
  9. 李白打酒java_蓝桥杯-李白打酒-java
  10. 谨慎选择镭射祛斑,极易反黑!一定要做好防晒,否则会变成永无止尽的黑斑地狱!
  11. 数字集成电路设计之加法器
  12. Redis文档--详解redis
  13. Android 头像定位图标
  14. 小程序创建搜索记录,获取搜索记录,删除搜索历史
  15. 求圆和直线之间的交点
  16. 315成央视盛宴是中国媒体的悲哀
  17. 渗透学什么?渗透测试中超全的提权思路来了!
  18. 2012年最值得学习的IT视频资料及技术资讯类网站列举
  19. Unity新GUI教程 – 第一部分
  20. C语言之玩转结构体1——结构体定义与声明

热门文章

  1. python教程吾爱破解_2020年最新python入门到精通教程
  2. php用哪个稳定版本linux系统,PHP的版本选择
  3. 补码中的+0和-0为什么机器码相同?
  4. Spark集群资源如何分配
  5. lr中错误解决方法(收集)
  6. 不同域名指向静态图片文件
  7. 锁-概念:可重入锁、可中断锁、公平锁、读写锁
  8. .Net中使用OracleDataAdapter
  9. 【Android】Mac安装EasyTether导致无法识别设备的问题
  10. [Android Samples视频系列之ApiDemos] App-Activity-Recreate