jQuery延迟对象简介

长期以来,JavaScript开发人员一直使用回调函数来执行多项任务。 一个非常常见的示例是在事件(例如clickkeypress )触发时,通过addEventListener()函数添加回调以执行各种操作。 回调函数很简单,可以在简单情况下完成工作。 不幸的是,当您的网页越来越复杂并且您需要并行或顺序执行许多异步操作时,它们将变得无法管理。

ECMAScript 2015(又名ECMAScript 6)引入了一种本地方法来应对这种情况:承诺。 如果您不知道什么是诺言,可以阅读文章JavaScript承诺概述 。 jQuery提供了并且仍然提供了自己的诺言,称为Deferred objects 。 在将承诺引入ECMAScript之前,它们就已引入jQuery多年。 在本文中,我将讨论什么是Deferred对象以及它们试图解决的问题。

一个简短的历史

Deferred对象是jQuery 1.5中引入的,它是一个可链接的实用程序,用于将多个回调注册到回调队列中,调用回调队列以及中继任何同步或异步函数的成功或失败状态。 从那时起,它一直是讨论的主题,一些批评和发展中的许多变化。 批评的例子包括“ 您错过了承诺的重点”和“ JavaScript的承诺”,以及为什么jQuery的实现被破坏了

DeferredPromise对象一起代表了Promise的jQuery实现。 在jQuery 1.x和2.x版本中, Deferred对象遵循CommonJS Promises / A建议 。 该提议被用作Promise / A +提议的基础,该提议是基于本地承诺的。 如引言中所述,jQuery之所以不遵守Promises / A +提议,是因为它甚至在构想该提议之前就实现了承诺方式。

由于jQuery是先驱,并且由于向后兼容的问题,在纯JavaScript以及jQuery 1.x和2.x中使用诺言的方式有所不同。 此外,由于jQuery遵循不同的建议,因此该库与实现了承诺的其他库(例如Q库 )不兼容。

在即将到来的jQuery 3中 ,与本机承诺(在ECMAScript 2015中实现)的互操作性已得到改善 。 出于向后兼容的原因,main方法( then() )的签名仍然有些不同,但是其行为与标准更加一致。

jQuery中的回调

为了理解为什么您可能需要使用Deferred对象,让我们讨论一个示例。 使用jQuery时,通常使用其Ajax方法执行异步请求。 出于示例的原因,假设您正在开发一个将Ajax请求发送到GitHub API的网页。 您的目标是检索用户存储库的列表,找到最新更新的存储库,找到名称中带有字符串“ README.md”的第一个文件,最后检索该文件的内容。 基于此描述,每个Ajax请求都只能在上一步完成时开始。 换句话说,请求必须按顺序运行。

将此描述转换为伪代码(请注意,我没有使用真正的GitHub API),我们得到:

var username = 'testuser';
var fileToSearch = 'README.md';

$.getJSON('https://api.github.com/user/' + username + '/repositories', function(repositories) {
  var lastUpdatedRepository = repositories[0].name;

  $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/files', function(files) {
    var README = null;

    for (var i = 0; i < files.length; i++) {
      if (files[i].name.indexOf(fileToSearch) >= 0) {
        README = files[i].path;

        break;
      }
    }

    $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/file/' + README + '/content', function(content) {
      console.log('The content of the file is: ' + content);
    });
  });
});

如本例所示,使用回调,我们必须嵌套调用以按所需顺序执行Ajax请求。 这使代码的可读性降低。 您有许多嵌套的回调或必须同步的独立回调的情况通常称为“回调地狱”。

为了使它更好一点,您可以从我创建的匿名内联函数中提取命名函数。 但是,此更改无济于事,我们仍然处于回调地狱。 输入“ Deferred和“ Promise对象。

延期和承诺对象

在执行异步操作(例如Ajax请求和动画)时,可以使用Deferred对象。 在jQuery中, Promise对象是从Deferred对象或jQuery对象创建的。 它拥有Deferred对象的方法的子集: always()done()fail()state()then() 。 在下一节中,我将介绍这些方法和其他方法。

如果您来自本地JavaScript世界,那么这两个对象的存在可能会让您感到困惑。 当JavaScript有一个对象( Promise )时,为什么要有两个对象( DeferredPromise )? 为了解释这些差异及其用例,我将采用与《 jQuery in Action》第三版中所使用的类推相同的类比。

如果编写用于处理异步操作的函数,并且应返回一个值(也可以是错误或根本没有值),则通常使用Deferred对象。 在这种情况下,您的函数是值的生产者 ,并且您希望防止用户更改Deferred的状态。 当您是函数的使用者时 ,将使用promise对象。

为了澄清这个概念,假设您要实现一个基于promise的timeout()函数(在本文的下一节中,我将向您显示此示例的代码)。 您是负责编写必须等待给定时间量的函数的人(在这种情况下,不会返回任何值)。 这使您成为制作人 。 函数的使用者不关心解决或拒绝它。 使用者只需要能够添加功能即可在Deferred的实现,失败或进展时执行。 此外,您要确保使用者无法自行决定解决或拒绝Deferred付款。 为了实现此目标,您需要返回在timeout()函数中创建的DeferredPromise对象,而不是Deferred本身。 这样,您可以确保除了timeout()函数之外,没有人可以调用resolve()reject()方法。

您可以在此StackOverflow问题中详细了解jQuery的Deferred和Promise对象之间的区别。

现在您知道了这些对象是什么,让我们看一下可用的方法。

递延方法

Deferred对象非常灵活,并提供了满足您所有需求的方法。 可以通过调用jQuery.Deferred()方法来创建它,如下所示:

var deferred = jQuery.Deferred();

或者,使用$快捷方式:

var deferred = $.Deferred();

创建后, Deferred对象将公开几种方法。 忽略那些已弃用或删除的内容,它们是:

  • always(callbacks[, callbacks, ..., callbacks]) :添加要解析或拒绝Deferred对象时要调用的处理程序。
  • done(callbacks[, callbacks, ..., callbacks]) :添加解析Deferred对象时要调用的处理程序。
  • fail(callbacks[, callbacks, ..., callbacks]) :添加拒绝Deferred对象时要调用的处理程序。
  • notify([argument, ..., argument]) :使用给定参数在Deferred对象上调用progressCallbacks
  • notifyWith(context[, argument, ..., argument]) :使用给定的上下文和参数在Deferred对象上调用progressCallbacks
  • progress(callbacks[, callbacks, ..., callbacks]) :添加Deferred对象生成进度通知时要调用的处理程序。
  • promise([target]) :返回DeferredPromise对象。
  • reject([argument, ..., argument]) :拒绝一个Deferred对象,并使用给定参数调用所有failCallbacks
  • rejectWith(context[, argument, ..., argument]) :拒绝一个Deferred对象,并使用给定的上下文和参数调用所有failCallbacks
  • resolve([argument, ..., argument]) :解析一个Deferred对象,并使用给定参数调用任何doneCallbacks
  • resolveWith(context[, argument, ..., argument]) :解析一个Deferred对象,并使用给定的上下文和参数调用任何doneCallbacks
  • state() :确定Deferred对象的当前状态。
  • then(resolvedCallback[, rejectedCallback[, progressCallback]]) :添加解析,拒绝或仍在处理Deferred对象时要调用的处理程序。

这些方法的描述使我有机会强调jQuery文档使用的术语与ECMAScript规范之间的区别。 在ECMAScript规范中,承诺被兑现或被兑现的承诺被解决。 但是,在jQuery的文档中,“解决”一词用于指ECMAScript规范称为实现状态的内容。

由于提供的方法很多,因此本文无法涵盖所有​​方法。 但是,在接下来的部分中,我将向您展示DeferredPromise的使用示例。 在第一个示例中,我们将重写在“ jQuery中的回调”一节中检查的代码段,但是我们将使用这些对象,而不是使用回调。 在第二个示例中,我将阐明所讨论的生产者-消费者类比。

递延的Ajax请求

在本节中,我将展示如何使用Deferred对象及其一些方法来提高“ jQuery回调”部分中开发的代码的可读性。 在深入研究之前,我们必须了解我们需要哪种可用的方法。

根据我们的要求和提供的方法列表,很明显,我们可以使用done()then()方法来管理成功的案例。 由于你们中的许多人可能已经习惯了JavaScript的Promise对象,因此在此示例中,我将采用then()方法。 这两种方法之间的一个重要区别是then()可以将作为参数接收的值转发给在then()定义的其他then()done()fail()progress()调用。

最终结果如下所示:

var username = 'testuser';
var fileToSearch = 'README.md';

$.getJSON('https://api.github.com/user/' + username + '/repositories')
  .then(function(repositories) {
    return repositories[0].name;
  })
  .then(function(lastUpdatedRepository) {
    return $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/files');
  })
  .then(function(files) {
    var README = null;

    for (var i = 0; i < files.length; i++) {
      if (files[i].name.indexOf(fileToSearch) >= 0) {
        README = files[i].path;

        break;
      }
    }

    return README;
  })
  .then(function(README) {
    return $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/file/' + README + '/content');
  })
  .then(function(content) {
    console.log(content);
  });

如您所见,代码具有更高的可读性,因为我们能够按相同级别(在缩进方面)的小步骤破坏整个过程。

创建基于承诺的setTimeout函数

如您所知, setTimeout()是一个在给定时间后执行回调函数的函数。 这两个元素(回调函数和时间)都应作为参数提供。 假设您想在一秒钟后将消息记录到控制台。 通过使用setTimeout()函数,可以使用以下代码实现此目标:

setTimeout(
  function() {
    console.log('I waited for 1 second!');
  },
  1000
);

如您所见,第一个参数是要执行的函数,第二个参数是要等待的毫秒数。 该功能已经运行了好几年,但是如果您需要在Deferred链中引入延迟怎么办?

在下面的代码中,我将向您展示如何使用jQuery提供的Promise对象来开发基于PromisesetTimeout()函数。 为此,我将使用Deferred对象的promise()方法。

最终结果如下所示:

function timeout(milliseconds) {
  // Create a new Deferred object
  var deferred = $.Deferred();

  // Resolve the Deferred after the amount of time specified by milliseconds
  setTimeout(deferred.resolve, milliseconds);

  // Return the Deferred's Promise object
  return deferred.promise();
}

timeout(1000).then(function() {
  console.log('I waited for 1 second!');
});

在此清单中,我定义了一个名为timeout()的函数,该函数包装了JavaScript的本机setTimeout()函数。 在timeout()内部,我创建了一个新的Deferred对象来管理异步任务,该任务包括在指定的毫秒数后解析Deferred对象。 在这种情况下, timeout()函数是值的生产者,因此它将创建Deferred对象并返回Promise对象。 这样,我确保函数的调用方(使用者)不能随意解析或拒绝Deferred对象。 实际上,调用者只能使用诸如done()fail()方法添加要执行的函数。

jQuery 1.x / 2.x和jQuery 3之间的区别

在使用Deferred的第一个示例中,我们开发了一个片段,该片段寻找名称中包含字符串“ README.md”的文件,但是我们没有考虑找不到该文件的情况。 这种情况可以视为失败。 发生这种情况时,我们可能想中断通话链,直接跳到结尾。 为此,就像使用JavaScript的catch()方法一样,自然会引发异常并使用fail()方法捕获它。

在Promises / A和Promises / A +兼容库(例如jQuery 3.x)中,引发的异常被转换为拒绝,并调用失败回调,例如添加了fail()的失败回调。 这将异常作为参数接收。

在jQuery 1.x和2.x中,未捕获的异常将暂停程序的执行。 这些版本允许抛出的异常冒泡,通常到达window.onerror 。 如果未定义任何函数来处理此异常,则会显示该异常的消息,并中止程序的执行。

为了更好地理解不同的行为,请看一下我的书中的以下示例:

var deferred = $.Deferred();
deferred
  .then(function() {
    throw new Error('An error message');
  })
  .then(
    function() {
      console.log('First success function');
    },
    function() {
      console.log('First failure function');
    }
  )
  .then(
    function() {
      console.log('Second success function');
    },
    function() {
      console.log('Second failure function');
    }
  );

deferred.resolve();

在jQuery 3.x中,此代码会将消息“第一个失败函数”和“第二个成功函数”写入控制台。 原因是,正如我之前提到的,规范指出应将抛出的异常转换为拒绝,并且必须使用该异常调用失败回调。 另外,一旦异常被管理(在我们的示例中,失败回调传递给第二个then() ),就应该执行以下成功函数(在这种情况下,成功回调传递给第三个then() )。

在jQuery 1.x和2.x中,仅执行第一个函数(抛出错误的函数),并且只在控制台上显示消息“ Uncaught Error:a error message”。

jQuery 1.x / 2.x

jsbin.com上的JS Bin

jQuery 3

jsbin.com上的JS Bin

为了进一步提高与ECMAScript 2015的兼容性,jQuery 3还向DeferredPromise对象添加了一个名为catch()的新方法。 这是一种定义处理程序的方法,该处理程序在Deferred对象被rejected或其Promise对象处于拒绝状态时执行。 其签名如下:

deferred.catch(rejectedCallback)

此方法不过是then(null, rejectedCallback)的快捷方式。

结论

在本文中,我向您介绍了jQuery的Promise实现。 Promise可以避免麻烦的技巧来同步并行异步函数,并且无需将回调嵌套在回调内部的回调中……

除了显示一些示例之外,我还介绍了jQuery 3如何通过本机Promise改进互操作性。 尽管旧版jQuery和ECMAScript 2015之间突出显示了差异,但Deferred仍然是工具箱中功能强大的工具。 作为专业开发人员,并且随着项目难度的增加,您会发现自己经常使用它。

From: https://www.sitepoint.com/introduction-jquery-deferred-objects/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值