jQuery之Deferred对象详解


Posted in Javascript onSeptember 04, 2014

deferred对象是jQuery对Promises接口的实现。它是非同步操作的通用接口,可以被看作是一个等待完成的任务,开发者通过一些通过的接口对其进行设置。事实上,它扮演代理人(proxy)的角色,将那些非同步操作包装成具有某些统一特性的对象,典型例子就是Ajax操作、网页动画、web worker等等。

jQuery的所有Ajax操作函数,默认返回的就是一个deferred对象。

Promises是什么

由于JavaScript单线程的特点,如果某个操作耗时很长,其他操作就必需排队等待。为了避免整个程序失去响应,通常的解决方法是将那些排在后面的操作,写成“回调函数”(callback)的形式。这样做虽然可以解决问题,但是有一些显著缺点:

1.回调函数往往写成函数参数的形式,导致函数的输入和输出非常混乱,整个程序的可阅读性差;
2.回调函数往往只能指定一个,如果有多个操作,就需要改写回调函数。
3.整个程序的运行流程被打乱,除错和调试的难度都相应增加。

Promises就是为了解决这些问题而提出的,它的主要目的就是取代回调函数,成为非同步操作的解决方案。它的核心思想就是让非同步操作返回一个对象,其他操作都针对这个对象来完成。比如,假定ajax操作返回一个Promise对象。

var promise = get('http://www.example.com');

然后,Promise对象有一个then方法,可以用来指定回调函数。一旦非同步操作完成,就调用指定的回调函数。
promise.then(function (content) {

  console.log(content)

})

可以将上面两段代码合并起来,这样程序的流程看得更清楚。
get('http://www.example.com').then(function (content) {

  console.log(content)

})

在1.7版之前,jQuery的Ajax操作采用回调函数。

$.ajax({

    url:"/echo/json/",

    success: function(response)

    {

       console.info(response.name);

    }

});

1.7版之后,Ajax操作直接返回Promise对象,这意味着可以用then方法指定回调函数。

$.ajax({

    url: "/echo/json/",

}).then(function (response) {

    console.info(response.name);

});

deferred对象的方法

$.deferred()方法

作用是生成一个deferred对象。

var deferred = $.deferred();

done() 和 fail()

这两个方法都用来绑定回调函数。done()指定非同步操作成功后的回调函数,fail()指定失败后的回调函数。

var deferred = $.Deferred();

deferred.done(function(value) {

   alert(value);

});

它们返回的是原有的deferred对象,因此可以采用链式写法,在后面再链接别的方法(包括done和fail在内)。

resolve() 和 reject()

这两个方法用来改变deferred对象的状态。resolve()将状态改为非同步操作成功,reject()改为操作失败。

var deferred = $.Deferred();

deferred.done(function(value) {

   alert(value);

});

deferred.resolve("hello world");

一旦调用resolve(),就会依次执行done()和then()方法指定的回调函数;一旦调用reject(),就会依次执行fail()和then()方法指定的回调函数。

state方法

该方法用来返回deferred对象目前的状态。

var deferred = new $.Deferred();

deferred.state();  // "pending"

deferred.resolve();

deferred.state();  // "resolved"

该方法的返回值有三个:

1.pending:表示操作还没有完成。
2.resolved:表示操作成功。
3.rejected:表示操作失败。

notify() 和 progress()

progress()用来指定一个回调函数,当调用notify()方法时,该回调函数将执行。它的用意是提供一个接口,使得在非同步操作执行过程中,可以执行某些操作,比如定期返回进度条的进度。

 var userProgress = $.Deferred();

    var $profileFields = $("input");

    var totalFields = $profileFields.length

    userProgress.progress(function (filledFields) {

        var pctComplete = (filledFields/totalFields)*100;

        $("#progress").html(pctComplete.toFixed(0));

    }); 

    userProgress.done(function () {

        $("#thanks").html("Thanks for completing your profile!").show();

    });

    $("input").on("change", function () {

        var filledFields = $profileFields.filter("[value!='']").length;

        userProgress.notify(filledFields);

        if (filledFields == totalFields) {

            userProgress.resolve();

        }

    });

then()

then()的作用也是指定回调函数,它可以接受三个参数,也就是三个回调函数。第一个参数是resolve时调用的回调函数,第二个参数是reject时调用的回调函数,第三个参数是progress()方法调用的回调函数。

deferred.then( doneFilter [, failFilter ] [, progressFilter ] )

在jQuery 1.8之前,then()只是.done().fail()写法的语法糖,两种写法是等价的。在jQuery 1.8之后,then()返回一个新的deferred对象,而done()返回的是原有的deferred对象。如果then()指定的回调函数有返回值,该返回值会作为参数,传入后面的回调函数。
var defer = jQuery.Deferred();

defer.done(function(a,b){

            return a * b;

}).done(function( result ) {

            console.log("result = " + result);

}).then(function( a, b ) {

            return a * b;

}).done(function( result ) {

            console.log("result = " + result);

}).then(function( a, b ) {

            return a * b;

}).done(function( result ) {

            console.log("result = " + result);

});

defer.resolve( 2, 3 );

在jQuery 1.8版本之前,上面代码的结果是:

result = 2 

result = 2 

result = 2

在jQuery 1.8版本之后,返回结果是
result = 2 

result = 6 

result = NaN

这一点需要特别引起注意。
$.ajax( url1, { dataType: "json" } )

.then(function( data ) {

    return $.ajax( url2, { data: { user: data.userId } } );

}).done(function( data ) {

  // 从url2获取的数据

});

上面代码最后那个done方法,处理的是从url2获取的数据,而不是从url1获取的数据。

利用then()会修改返回值这个特性,我们可以在调用其他回调函数之前,对前一步操作返回的值进行处理。

var post = $.post("/echo/json/")

    .then(function(p){

        return p.firstName;

    });

post.done(function(r){ console.log(r); });

上面代码先使用then()方法,从返回的数据中取出所需要的字段(firstName),所以后面的操作就可以只处理这个字段了。

有时,Ajax操作返回json字符串里面有一个error属性,表示发生错误。这个时候,传统的方法只能是通过done()来判断是否发生错误。通过then()方法,可以让deferred对象调用fail()方法。

var myDeferred = $.post('/echo/json/', {json:JSON.stringify({'error':true})})

    .then(function (response) {

            if (response.error) {

                return $.Deferred().reject(response);

            }

            return response;

        },function () {

            return $.Deferred().reject({error:true});

        }

    );

myDeferred.done(function (response) {

        $("#status").html("Success!");

    }).fail(function (response) {

        $("#status").html("An error occurred");

    });

always()

always()也是指定回调函数,不管是resolve或reject都要调用。

pipe方法

pipe方法接受一个函数作为参数,表示在调用then方法、done方法、fail方法、always方法指定的回调函数之前,先运行pipe方法指定的回调函数。它通常用来对服务器返回的数据做初步处理。

promise对象

大多数情况下,我们不想让用户从外部更改deferred对象的状态。这时,你可以在deferred对象的基础上,返回一个针对它的promise对象。我们可以把后者理解成,promise是deferred的只读版,或者更通俗地理解成promise是一个对将要完成的任务的承诺。

你可以通过promise对象,为原始的deferred对象添加回调函数,查询它的状态,但是无法改变它的状态,也就是说promise对象不允许你调用resolve和reject方法。

function getPromise(){

    return $.Deferred().promise();

}

try{

    getPromise().resolve("a");

} catch(err) {

    console.log(err);

}

上面的代码会出错,显示TypeError {} 。

jQuery的ajax() 方法返回的就是一个promise对象。此外,Animation类操作也可以使用promise对象。

var promise = $('div.alert').fadeIn().promise();

$.when()方法

$.when()接受多个deferred对象作为参数,当它们全部运行成功后,才调用resolved状态的回调函数,但只要其中有一个失败,就调用rejected状态的回调函数。它相当于将多个非同步操作,合并成一个。

$.when(

    $.ajax( "/main.php" ),

    $.ajax( "/modules.php" ),

    $.ajax( "/lists.php" )

).then(successFunc, failureFunc);

上面代码表示,要等到三个ajax操作都结束以后,才执行then方法指定的回调函数。

when方法里面要执行多少个操作,回调函数就有多少个参数,对应前面每一个操作的返回结果。

$.when(

    $.ajax( "/main.php" ),

    $.ajax( "/modules.php" ),

    $.ajax( "/lists.php" )

).then(function (resp1, resp2, resp3){

    console.log(resp1);

    console.log(resp2);

    console.log(resp3);

});

上面代码的回调函数有三个参数,resp1、resp2和resp3,依次对应前面三个ajax操作的返回结果。

when方法的另一个作用是,如果它的参数返回的不是一个Deferred或Promise对象,那么when方法的回调函数将 立即运行。

$.when({testing: 123}).done(function (x){

  console.log(x.testing); // "123"

});

上面代码中指定的回调函数,将在when方法后面立即运行。

利用这个特点,我们可以写一个具有缓存效果的异步操作函数。也就是说,第一次调用这个函数的时候,将执行异步操作,后面再调用这个函数,将会返回缓存的结果。

function maybeAsync( num ) {

  var dfd = $.Deferred();

  if ( num === 1 ) {

    setTimeout(function() {

      dfd.resolve( num );

    }, 100);

    return dfd.promise();

  }

  return num;

}

$.when(maybeAsync(1)).then(function (resp){

  $('#target').append('<p>' + resp + '</p>');

});

$.when(maybeAsync(0)).then(function (resp){

  $('#target').append( '<p>' + resp + '</p>');

});

上面代码表示,如果maybeAsync函数的参数为1,则执行异步操作,否则立即返回缓存的结果。

实例

wait方法

我们可以用deferred对象写一个wait方法,表示等待多少毫秒后再执行。

$.wait = function(time) {

  return $.Deferred(function(dfd) {

    setTimeout(dfd.resolve, time);

  });

}

使用方法如下:
$.wait(5000).then(function() {

  alert("Hello from the future!");

});

改写setTimeout方法

在上面的wait方法的基础上,还可以改写setTimeout方法,让其返回一个deferred对象。

function doSomethingLater(fn, time) {

  var dfd = $.Deferred();

  setTimeout(function() {

    dfd.resolve(fn());

  }, time || 0);

  return dfd.promise();

}

var promise = doSomethingLater(function (){

  console.log( '已经延迟执行' );

}, 100);

自定义操作使用deferred接口

我们可以利用deferred接口,使得任意操作都可以用done()和fail()指定回调函数。

Twitter = {

  search:function(query) {

    var dfr = $.Deferred();

    $.ajax({

     url:"http://search.twitter.com/search.json",

     data:{q:query},

     dataType:'jsonp',

     success:dfr.resolve

    });

    return dfr.promise();

  }

}

使用方法如下:

Twitter.search('intridea').then(function(data) {

  alert(data.results[0].text);

});

deferred对象的另一个优势是可以附加多个回调函数。
function doSomething(arg) {

  var dfr = $.Deferred();

  setTimeout(function() {

    dfr.reject("Sorry, something went wrong.");

  });

  return dfr;

}

doSomething("uh oh").done(function() {

  alert("Won't happen, we're erroring here!");

}).fail(function(message) {

  alert(message)

});
Javascript 相关文章推荐
javascript入门·动态的时钟,显示完整的一些方法,新年倒计时
Oct 01 Javascript
JavaScript 创建对象
Jul 17 Javascript
jQuery隔行变色与普通JS写法的对比
Apr 21 Javascript
你必须知道的Javascript知识点之&quot;深入理解作用域链&quot;的介绍
Apr 23 Javascript
JavaScript调用客户端的可执行文件(示例代码)
Nov 28 Javascript
javascript的tab切换原理与效果实现方法
Jan 10 Javascript
Ajax清除浏览器js、css、图片缓存的方法
Aug 06 Javascript
JavaScript中防止微信浏览器被整体拖动的方法
Aug 25 Javascript
解决vue 路由变化页面数据不刷新的问题
Mar 13 Javascript
JavaScript实现京东购物放大镜和选项卡效果的方法分析
Jul 05 Javascript
js实现一个页面多个倒计时的3种方法
Feb 25 Javascript
微信小程序音乐播放器开发
Nov 20 Javascript
Javascript Objects详解
Sep 04 #Javascript
加随机数引入脚本不让浏览器读取缓存
Sep 04 #Javascript
js不能获取隐藏的div的宽度只能先显示后获取
Sep 04 #Javascript
点击A元素触发B元素的事件在IE8下会识别成A元素
Sep 04 #Javascript
Flexigrid在IE下不显示数据的有效处理方法
Sep 04 #Javascript
js自动生成的元素与页面原有元素发生堆叠的解决方法
Sep 04 #Javascript
告诉你什么是javascript的回调函数
Sep 04 #Javascript
You might like
PHP详解ASCII码对照表与字符转换
2011/12/05 PHP
PHP HTML JavaScript MySQL代码如何互相传值的方法分享
2012/09/30 PHP
PHP中PDO连接数据库中各种DNS设置方法小结
2016/05/13 PHP
通过MSXML2自动获取QQ个人头像及在线情况(给初学者)
2007/01/22 Javascript
jquery数据验证插件(自制,简单,练手)实例代码
2013/10/24 Javascript
js 判断浏览器使用的语言示例代码
2014/03/22 Javascript
javascript超过容器后显示省略号效果的方法(兼容一行或者多行)
2016/07/14 Javascript
使用vue.js编写蓝色拼图小游戏
2017/03/17 Javascript
原生js实现省市区三级联动代码分享
2018/02/12 Javascript
基于Vue插入视频的2种方法小结
2019/04/02 Javascript
JS实现电脑虚拟键盘的操作
2020/06/24 Javascript
详解三种方式在React中解决绑定this的作用域问题并传参
2020/08/18 Javascript
[01:09]2014DOTA2国际邀请赛 TI4西雅图DOTA2 中国美女coser加油助威
2014/07/20 DOTA
决策树的python实现方法
2014/11/18 Python
Python批量修改文本文件内容的方法
2016/04/29 Python
python web框架学习笔记
2016/05/03 Python
python僵尸进程产生的原因
2017/07/21 Python
Python实现输出程序执行进度百分比的方法
2017/09/16 Python
python线程中同步锁详解
2018/04/27 Python
Python读写及备份oracle数据库操作示例
2018/05/17 Python
Django框架之登录后自定义跳转页面的实现方法
2019/07/18 Python
HTML5是什么 HTML5是什么意思 HTML5简介
2012/10/26 HTML / CSS
波兰香水和化妆品购物网站:Notino.pl
2017/11/07 全球购物
如何设置Java的运行环境
2013/04/05 面试题
会计与审计专业大专生求职信
2013/10/03 职场文书
《有趣的发现》教学反思
2014/04/15 职场文书
《中国梦我的梦》大学生演讲稿
2014/08/20 职场文书
大学军训的体会
2014/11/08 职场文书
2014年便民服务中心工作总结
2014/12/20 职场文书
卫生保健工作总结2015
2015/05/18 职场文书
电影建党伟业观后感
2015/06/01 职场文书
2015暑期社会实践个人总结
2015/07/13 职场文书
公司客户答谢酒会祝酒词
2015/08/11 职场文书
pytorch finetuning 自己的图片进行训练操作
2021/06/05 Python
SpringBoot快速入门详解
2021/07/21 Java/Android
浅谈MySql update会锁定哪些范围的数据
2022/06/25 MySQL