一、什么是deferred对象?

开发网站的过程中,我们经常遇到某些耗时很长的javascript操作。其中,既有异步的操作(比如ajax读取服务器数据),也有同步的操作(比如遍历一个大型数组),它们都不是立即能得到结果的。

通常的做法是,为它们指定回调函数(callback)。即事先规定,一旦它们运行结束,应该调用哪些函数。

但是,在回调函数方面,jQuery的功能非常弱。为了改变这一点,jQuery开发团队就设计了deferred对象

简单说,deferred对象就是jQuery的回调函数解决方案。在英语中,defer的意思是"延迟",所以deferred对象的含义就是"延迟"到未来某个点再执行。

它解决了如何处理耗时操作的问题,对那些操作提供了更好的控制,以及统一的编程接口。它的主要功能,可以归结为四点。下面我们通过示例代码,一步步来学习。

来看下面的一段代码

  1. function step1(callback) {
  2. $.ajax({
  3. url : '/api/test',
  4. type : 'POST',
  5. data : {
  6. ...
  7. },
  8. success : function (res) {
  9. callback && callback();
  10. }
  11. })
  12. }

当我们使用回调来解决实际中的问题时,很容易不知不觉中出现代码金字塔,嵌套层次就会越深,代码可读性就会越差。

  1. step1(function () {
  2. step2(function () {
  3. step3(function () {
  4. step4(function () {
  5. step5();
  6. })
  7. })
  8. })
  9. })

ES6 原生提供了 Promise 对象。

所谓 Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。

Promise 对象有以下两个特点。

(1)对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

基本的 api

  1. Promise.resolve()

  2. Promise.reject()

  3. Promise.prototype.then()

  4. Promise.prototype.catch()

  5. Promise.all() // 所有的完成

所谓“承诺”

Promise这个单词的意思是“承诺”,就是我们日常说的“我肯定帮你买早饭”、“你如果交了钱我肯定给你一杯咖啡”。

在程序世界,举例可以说:“我承诺给你完成这些代码执行”。new一个Promise实例,就是JS引擎对你做了一个承诺。

既然是承诺,就肯定有成功的时候有失败的时候,比如我帮你买早餐,结果今天煎饼果子没出摊,或者是我走到半路上,煎饼果子的塑料袋破裂,煎饼果子滑落到了地上,这就是失败。就连“我们承诺绝不首先动用核武器”都有坚持不下去的时候,所以只要是承诺就有成功和失败,只不过是概率问题。

到程序世界,一个承诺也会有三种状态,就是“未决的”、“成功的”、“失败的”三种状态。也就是pendding/resolved/rejected三种状态。

Promise构造函数的超能力

Promises写法的本质就是把异步写法撸成同步写法。要做这么酷炫这么变态的事情,当然需要Promise构造函数有超能力,它的超能力就是传入Promise构造函数的函数参数会第一优先执行,无论这个函数多么的繁复,有多少层回调,有多少秒的计数器,统统都会最优先执行,也就是说,我们只要new了一个Promise(),那么Promise构造函数的函数参数就是最高优先级执行,一直到new出一个promise对象实例,后面的.then()代码才会执行。链条上的每一个.then都会等前面的promise有了结果才会执行,Promise构造函数的这个超能力是Promises系统的威力之源。(当然,这里说的执行优先级,是在理想环境下,所谓理想环境也就是全部执行代码只由new Promise()和它的一系列.then()方法组成。如果方法链之外还有其他代码,那么整体代码执行的先后顺序就复杂化了,下文有介绍。然而,Promise加它的then方法链已经提供了梳理代码执行顺序的整套方案,如果在方法链之外还写代码的话属于画蛇添足、自找麻烦,是不科学的写法,应该避免这么做。)

ajax操作的链式写法

  1.  $.ajax("test.html")
  2.  
  3.   .done(function(){ alert("哈哈,成功了!"); })
  4.  
  5.   .fail(function(){ alert("出错啦!"); });

deferred对象的最大优点,就是它把这一套回调函数接口,从ajax操作扩展到了所有操作。也就是说,任何一个操作----不管是ajax操作还是本地操作,也不管是异步操作还是同步操作----都可以使用deferred对象的各种方法,指定回调函数。

我们来看一个具体的例子。假定有一个很耗时的操作wait:

  1. var wait = function(){
  2.  
  3.     var tasks = function(){
  4.  
  5.       alert("执行完毕!");
  6.  
  7.     };
  8.  
  9.     setTimeout(tasks,5000);
  10.  
  11.   };

但是,这样写的话,done()方法会立即执行,起不到回调函数的作用。原因在于$.when()的参数只能是deferred对象,所以必须对wait()进行改写:

  1. var dtd = $.Deferred(); // 新建一个deferred对象
  2.  
  3.   var wait = function(dtd){
  4.  
  5.     var tasks = function(){
  6.  
  7.       alert("执行完毕!");
  8.  
  9.       dtd.resolve(); // 改变deferred对象的执行状态
  10.  
  11.     };
  12.  
  13.     setTimeout(tasks,5000);
  14.  
  15.     return dtd;
  16.  
  17.   };

现在,wait()函数返回的是deferred对象,这就可以加上链式操作了。

  1.  $.when(wait(dtd))
  2.  
  3.   .done(function(){ alert("哈哈,成功了!"); })
  4.  
  5.   .fail(function(){ alert("出错啦!"); });

wait()函数运行完,就会自动运行done()方法指定的回调函数。

deferred.resolve()方法和deferred.reject()方法

要说清楚这个问题,就要引入一个新概念"执行状态"。jQuery规定,deferred对象有三种执行状态----未完成,已完成和已失败。如果执行状态是"已完成"(resolved),deferred对象立刻调用done()方法指定的回调函数;如果执行状态是"已失败",调用fail()方法指定的回调函数;如果执行状态是"未完成",则继续等待,或者调用progress()方法指定的回调函数(jQuery1.7版本添加)。

前面部分的ajax操作时,deferred对象会根据返回结果,自动改变自身的执行状态;但是,在wait()函数中,这个执行状态必须由程序员手动指定。dtd.resolve()的意思是,将dtd对象的执行状态从"未完成"改为"已完成",从而触发done()方法。

类似的,还存在一个deferred.reject()方法,作用是将dtd对象的执行状态从"未完成"改为"已失败",从而触发fail()方法。

  1. var dtd = $.Deferred(); // 新建一个Deferred对象
  2.  
  3.   var wait = function(dtd){
  4.  
  5.     var tasks = function(){
  6.  
  7.       alert("执行完毕!");
  8.  
  9.       dtd.reject(); // 改变Deferred对象的执行状态
  10.  
  11.     };
  12.  
  13.     setTimeout(tasks,5000);
  14.  
  15.     return dtd;
  16.  
  17.   };
  18.  
  19.   $.when(wait(dtd))
  20.  
  21.   .done(function(){ alert("哈哈,成功了!"); })
  22.  
  23.   .fail(function(){ alert("出错啦!"); });

deferred.promise()方法

上面这种写法,还是有问题。那就是dtd是一个全局对象,所以它的执行状态可以从外部改变。

请看下面的代码:

  1. var dtd = $.Deferred(); // 新建一个Deferred对象
  2.  
  3.   var wait = function(dtd){
  4.  
  5.     var tasks = function(){
  6.  
  7.       alert("执行完毕!");
  8.  
  9.       dtd.resolve(); // 改变Deferred对象的执行状态
  10.  
  11.     };
  12.  
  13.     setTimeout(tasks,5000);
  14.  
  15.     return dtd;
  16.  
  17.   };
  18.  
  19.   $.when(wait(dtd))
  20.  
  21.   .done(function(){ alert("哈哈,成功了!"); })
  22.  
  23.   .fail(function(){ alert("出错啦!"); });
  24.  
  25.   dtd.resolve();

我在代码的尾部加了一行dtd.resolve(),这就改变了dtd对象的执行状态,因此导致done()方法立刻执行,跳出"哈哈,成功了!"的提示框,等5秒之后再跳出"执行完毕!"的提示框。

为了避免这种情况,jQuery提供了deferred.promise()方法。它的作用是,在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。

请看下面的代码:

  1. var dtd = $.Deferred(); // 新建一个Deferred对象
  2.  
  3.   var wait = function(dtd){
  4.  
  5.     var tasks = function(){
  6.  
  7.       alert("执行完毕!");
  8.  
  9.       dtd.resolve(); // 改变Deferred对象的执行状态
  10.  
  11.     };
  12.  
  13.     setTimeout(tasks,5000);
  14.  
  15.     return dtd.promise(); // 返回promise对象
  16.  
  17.   };
  18.  
  19.   var d = wait(dtd); // 新建一个d对象,改为对这个对象进行操作
  20.  
  21.   $.when(d)
  22.  
  23.   .done(function(){ alert("哈哈,成功了!"); })
  24.  
  25.   .fail(function(){ alert("出错啦!"); });
  26.  
  27.   d.resolve(); // 此时,这个语句是无效的

在上面的这段代码中,wait()函数返回的是promise对象。然后,我们把回调函数绑定在这个对象上面,而不是原来的deferred对象上面。这样的好处是,无法改变这个对象的执行状态,要想改变执行状态,只能操作原来的deferred对象。

不过,更好的写法是将dtd对象变成wait()函数的内部对象。

  1.  var wait = function(dtd){
  2.  
  3.     var dtd = $.Deferred(); //在函数内部,新建一个Deferred对象
  4.  
  5.     var tasks = function(){
  6.  
  7.       alert("执行完毕!");
  8.  
  9.       dtd.resolve(); // 改变Deferred对象的执行状态
  10.  
  11.     };
  12.  
  13.     setTimeout(tasks,5000);
  14.  
  15.     return dtd.promise(); // 返回promise对象
  16.  
  17.   };
  18.  
  19.   $.when(wait())
  20.  
  21.   .done(function(){ alert("哈哈,成功了!"); })
  22.  
  23.   .fail(function(){ alert("出错啦!"); });

普通操作的回调函数接口(中)

另一种防止执行状态被外部改变的方法,是使用deferred对象的建构函数$.Deferred()。

这时,wait函数还是保持不变,我们直接把它传入$.Deferred():

  1.  $.Deferred(wait)
  2.  
  3.   .done(function(){ alert("哈哈,成功了!"); })
  4.  
  5.   .fail(function(){ alert("出错啦!"); });

除了上面两种方法以外,我们还可以直接在wait对象上部署deferred接口。

  1. var dtd = $.Deferred(); // 生成Deferred对象
  2.  
  3.   var wait = function(dtd){
  4.  
  5.     var tasks = function(){
  6.  
  7.       alert("执行完毕!");
  8.  
  9.       dtd.resolve(); // 改变Deferred对象的执行状态
  10.  
  11.     };
  12.  
  13.     setTimeout(tasks,5000);
  14.  
  15.   };
  16.  
  17.   dtd.promise(wait);
  18.  
  19.   wait.done(function(){ alert("哈哈,成功了!"); })
  20.  
  21.   .fail(function(){ alert("出错啦!"); });
  22.  
  23.   wait(dtd);

这里的关键是dtd.promise(wait)这一行,它的作用就是在wait对象上部署Deferred接口。正是因为有了这一行,后面才能直接在wait上面调用done()和fail()。

小结:deferred对象的方法

前面已经讲到了deferred对象的多种方法,下面做一个总结:

  (1) $.Deferred() 生成一个deferred对象。

  (2) deferred.done() 指定操作成功时的回调函数

  (3) deferred.fail() 指定操作失败时的回调函数

  (4) deferred.promise() 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口。

  (5) deferred.resolve() 手动改变deferred对象的运行状态为"已完成",从而立即触发done()方法。

  (6)deferred.reject() 这个方法与deferred.resolve()正好相反,调用后将deferred对象的运行状态变为"已失败",从而立即触发fail()方法。

  (7) $.when() 为多个操作指定回调函数。

除了这些方法以外,deferred对象还有二个重要方法,上面的教程中没有涉及到。

  (8)deferred.then()

有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。

  1.  $.when($.ajax( "/main.php" ))
  2.  
  3.   .then(successFunc, failureFunc );

如果then()有两个参数,那么第一个参数是done()方法的回调函数,第二个参数是fail()方法的回调方法。如果then()只有一个参数,那么等同于done()。

deferred.always()

这个方法也是用来指定回调函数的,它的作用是,不管调用的是deferred.resolve()还是deferred.reject(),最后总是执行

  1.  $.ajax( "test.html" )
  2.  
  3.   .always( function() { alert("已执行!");} );

本文转载:http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html

如果这篇文章对您有帮助,您可以打赏我

技术交流QQ群:15129679

关于Promise的一些个人理解jQuery的deferred的更多相关文章

  1. 深入理解jQuery、Angular、node中的Promise

    最初遇到Promise是在jQuery中,在jQuery1.5版本中引入了Deferred Object,这个异步队列模块用于实现异步任务和回调函数的解耦.为ajax模块.队列模块.ready事件提供 ...

  2. 大白话讲解Promise(二)理解Promise规范

    上一篇我们讲解了ES6中Promise的用法,但是知道了用法还远远不够,作为一名专业的前端工程师,还必须通晓原理.所以,为了补全我们关于Promise的知识树,有必要理解Promise/A+规范,理解 ...

  3. 深入分析,理解jQuery.Deferred源码

    前言: 如果你对jQuery.Callback回调对象不了解,或者只掌握其方法,但是没有通过阅读源码理解,可以先阅读 前一章jQuery.Callbacks源码解读二,因为只有完全理解jQuery.C ...

  4. 关于 Promise 的一些简单理解

    一.ES6 中的 Promise 1.JS 如何解决 异步问题? (1)什么是 同步.异步? 同步指的是 需要等待 前一个处理 完成,才会进行 下一个处理. 异步指的是 不需要等待 前一个处理 完成, ...

  5. 深度理解Jquery 中 offset() 方法

    参考原文:深度理解Jquery 中 offset() 方法

  6. 深入理解jQuery中的Deferred

    引入 1  在开发的过程中,我们经常遇到某些耗时很长的javascript操作,并且伴随着大量的异步. 2  比如我们有一个ajax的操作,这个ajax从发出请求到接收响应需要5秒,在这5秒内我们可以 ...

  7. 从html页面加载顺序来更好的理解jquery初始化

    一,html页面加载顺序 1,用户输入网址(假设是个html页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回html文件:2,浏览器开始载入html代码,发现<head>标签内 ...

  8. 简单理解jQuery中$.getJSON、$.get、$.post、$.ajax用法

    在WEB开发中异步请求方式普遍使用,ajax技术减少程序员的工作量,也提升用户交互体验.AJAX的四种异步请求方式都能实现基本需求,闲话不多说,直接切入正题. 1.$.getJSON $.getJSO ...

  9. ajax和springmvc的请求响应原理——深入理解jQuery中$.get、$.post、$.getJSON和$.ajax的用法

    1,四大重要部分: 请求链接 post请求和get请求 请求参数形式 响应内容形式 2,从springmvc的controller角度,controller能接收到请求的前提 请求链接必须对应 pos ...

随机推荐

  1. bitnami-redmine邮件告警配置

    配置 bitnami-redmine的配置文件与单纯的redmine配置文件可能并不相同,在这里我们需要打开一下配置文件: /opt/bitnami/apps/redmine/htdocs/confi ...

  2. 使用 NuGet 管理我们的程序集 - 预发行版

    1.缘起 在我们的项目中.须要引用的组件统一放在一个 Libs 文件夹下.不管对于平台上的公共组件.还是应用模块,都是如此. 假设一个应用模块,比如能源管理(EM).要引用平台提供的公共组件,比如数据 ...

  3. StatCounter

    StatCounter provides free customisable hit counters, visitor tracking, web analytics and website sta ...

  4. delphi 使用工控机控件 iThreadTimes 出现问题, 导致主程序创建页面的时候, 阻塞消息, 不能正常执行。

    delphi  使用工控机控件 iThreadTimes 出现问题, 导致主程序创建页面的时候, 阻塞消息, 不能正常执行. 使用这个控件需要小心 function Tfrm_MainIPC.Open ...

  5. JPA & Hibernate 注解

    1 . @Entity(name="EntityName") 必须 ,name 为可选 , 对应数据库中一的个表 2 . @Table(name="",cata ...

  6. NSURLRequest with UserAgent

    关于iOS上的http请求还在不断学习,从早先的时候发现原来iOS的http请求可以自动保存cookie到后来的,发现ASIHttpRequest会有User-Agent,到现在发现竟然NSURLRe ...

  7. 三张图让你高速明确activity与fragment生命周期的异同点

    第一张图:activity的生命周期 第二张图:fragment的生命周期 第三张图:activity与fragment生命周期对照 补充:假设你还是不明确,请翻译一下你不理解的相应单词. ----- ...

  8. docker 查看容器的网络连接

    #! /bin/bash echo $1 PID=$(docker inspect -f '{{.State.Pid}}' $1) nsenter -t $PID -n netstat |grep E ...

  9. End2endIT

    "C:\Program Files\Java\jdk1.8.0_112\bin\java" -ea -Didea.test.cyclic.buffer.size=1048576 & ...

  10. 用IO流向存储器或SD卡中存入/读取字符的工具类

    FileManager package com.kale.utils; import java.io.BufferedReader; import java.io.File; import java. ...