jQuery Deferred Objects(递延对象)
jQuery中的递延对象表示稍后将完成的工作单元,通常是异步完成。工作单元完成后,延迟对象可以设置为已解决或失败。
延迟对象包含承诺对象。通过承诺对象,您可以指定工作单元完成后会发生什么。您可以通过在承诺对象上设置回调函数来做到这一点。在本文本稍后,我将向您展示所有这些是如何工作的。
这是一张图表,显示了jQuery的延迟对象的工作原理。如果你现在不了解这一切,别担心。稍后会解释。然后,您可以在阅读说明后返回此图表。
典型的延迟对象用例
延迟对象通常用于您通常传递回调函数的任何地方,以便在某些异步工作单元完成后执行。执行异步工作最常见的情况是:
AJAX电话
在网络上加载资源
setTimeout()或setInterval()
动画
用户互动
当执行AJAX调用时,浏览器将异步执行。一旦AJAX调用完成,浏览器将调用一些回调函数。
使用JavaScript通过网络加载图像时,您可以指定一个回调函数,以便在图像完全加载时调用。加载图像本质上与AJAX调用非常相似。两者都是通过网络发送的HTTP请求。
当您使用JavaScript setTimeout()
和setInterval()
函数时,您将传递一些回调函数,以便在超时时执行。
动画通常运行一段时间。动画完成后,您可能想执行一些操作,要么在动画后清理,要么进行一些与动画相关的其他操作。例如,如果您为列表中的项目删除添加动画效果,您可能希望通过在数据结构或DOM中实际从列表中删除该项目来完成。
用户交互是另一种典型的异步情况。您的代码可能会打开一个对话框,并指定当对话框关闭时,只需按下“确定”按钮或使用“取消”按钮会发生什么。有时,您可以通过在“确定”和“取消”按钮上设置显式侦听器函数来绕过这种异步情况,而不是将回调传递给打开对话框的函数。不过,两者都是可行的选择。
使用jQuery的延迟对象
在展示延迟对象在使用中的外观之前,让我首先向您展示没有延迟对象的标准异步函数的外观。以下示例函数将回调函数作为参数,该参数在特定时间延迟后执行。
function doAsync(callback){ setTimeout(function() { callback(); }, 1000); }复制代码
使用回调调用doAsync()
函数如下所示:
doAsync(function() { console.log("Executed after a delay"); });复制代码
延迟后,将执行作为参数传递给doAsync()
的函数。
doAsync()
函数也可以使用jQuery延迟对象实现。以下是doAsync()
函数(称为doAsync2()
的jQuery延迟对象示例实现:
function doAsync2() { var deferredObject = $.Deferred(); setTimeout(function() { deferredObject.resolve(); }, 1000); return deferredObject.promise(); }复制代码
首先使用jQuery $. deferred()函数创建一个deferred对象。延迟对象本身不做任何事情。它只是一些异步工作的表示。
其次,将函数传递给setTimeout()
函数。此函数在延迟后执行。该函数调用deferredObject.resolve()
。这会导致延迟对象的内部状态更改为已解决(延迟后)。
最后,返回与延迟对象关联的承诺对象。通过承诺对象,调用doAsync2()
函数的代码可以决定如果延迟对象的状态更改为已解决或失败,会发生什么。
调用doAsync2()
函数如下所示:
var promise = doAsync2(); promise.done(function () { console.log("Executed after a delay"); });复制代码
注意回调函数不再作为参数传递给doAsync2()函数。相反,回调函数被传递给promise对象上的done()函数。一旦被延迟对象的状态被解析,传递给done()的回调函数将被执行。
如果延迟对象的状态更改为失败,则传递给 done()
回调函数将不会执行。相反,传递给承诺对象的fail()
函数的回调函数将被执行。以下是将回调函数传递给fail()
函数的方法:
var promise = doAsync2(); promise.done(function () { console.log("Executed after a delay"); }); promise.fail(function () { console.log("Executed if the async work fails"); }); 复制代码
此外,由于done()和fail()函数返回promise对象本身,你可以将上述代码缩短为:
doAsync2() .done(function () { console.log("Executed after a delay"); }).fail(function () { console.log("Executed if the async work fails"); });复制代码
当然,doAsync2()
函数的实现永远不会将延迟对象的状态设置为失败,因此此函数永远不会在本例中执行。让我们更改doAsync2()
(如doAsync3()
)的实现,以便延迟的对象实际上可以将状态更改为失败。
function doAsync3() { var deferredObject = $.Deferred(); setTimeout(function() { var randomValue = Math.random(); if(randomValue < 0.5) { deferredObject.resolve(); } else { deferredObject.reject(); } }, 1000); return deferredObject.promise(); }复制代码
在此实现中,异步调用的函数生成0到1之间的随机数。如果数字小于0.5,则延迟对象的状态设置为解析。否则,延迟对象的状态设置为失败。这是通过调用延迟对象的reject()
函数来实现的。
延迟对象与承诺对象
如果你查看jQuery文档,你会发现一个deferred对象也有一个done()和fail()函数,就像promise对象一样。这意味着,您可以返回延迟对象本身,而不是从延迟对象返回promise对象。下面是它的样子:
function doAsync4() { var deferredObject = $.Deferred(); setTimeout(function() { var randomValue = Math.random(); if(randomValue < 0.5) { deferredObject.resolve(); } else { deferredObject.reject(); } }, 1000); return deferredObject; }复制代码
doAsync4()
和doAsync3()
的唯一区别是最后一行。它返回的不是deferredObject.promise()
,而是返回deferredObject
。
如何使用doAsync4()和doAsync3()没有什么不同。你也可以直接调用被延迟对象的done()和fail()。其效果与在promise对象上调用这些函数相同。
返回延迟对象和返回承诺对象的主要区别是,延迟对象可用于设置延迟对象的解析或失败状态。你不能在承诺对象上那样做。因此,如果您不希望异步函数的用户能够修改延迟对象的状态,最好不要返回延迟对象,而只返回其承诺对象(deferredObject.promise()
)。
承诺对象
延迟对象返回的承诺对象包含以下您可以调用的函数:
done()
fail()
always()
progress()
then()
state()
我们已经看过的前两个函数,done()
和fail()
)。当连接到承诺对象的延迟对象将其状态更改为已解决或失败时,将调用它们。
always()函数也接受一个回调函数作为参数。当延迟对象将状态更改为已解析或失败时,总是调用此回调函数。延迟对象更改到哪个状态并不重要。当状态改变时,首先调用传递给done()或fail()的回调函数,然后调用传递给always()的回调函数
progress()
函数可用于将进度回调函数附加到承诺对象。每当在连接到承诺对象的延迟对象上调用notify()
函数时,都会调用此回调函数。该系统可用于通知侦听器长期运行的异步操作的进度。在本文本后面,我将向您展示如何做到这一点的示例。
then()函数允许链接和过滤承诺对象。您还可以将它用作为已解析和失败状态更改设置回调函数的快捷方式,而不是使用done()、fail()和progress()。在本文的后面,我将展示如何使用then()函数。
state()
函数返回连接到承诺对象的延迟对象的状态。它将返回其中一个字符串“待定”、“已解决”或“已拒绝”。字符串“待定”表示尚未解析或拒绝的延迟对象的状态。
将多个回调附加到Promise对象
您可以为每个延迟对象状态附加多个回调函数到一个承诺对象。以下是一个例子:
var promise = doAsync3();promise.done(function() { console.log("done 1"); } );promise.done(function() { console.log("done 2"); } );promise.fail(function() { console.log("fail 1"); } );promise.fail(function() { console.log("fail 2"); } );promise.always(function() { console.log("ways 1"); } );promise.always(function() { console.log("always 2"); } );复制代码
这个例子使用每个done()、fail()和always()函数附加了2个回调函数。当被延迟的对象改变其状态时,回调函数将按其注册的顺序被调用。
解决或拒绝延迟对象
您可以使用延迟对象的resolve()
和reject()
函数解析或拒绝延迟对象。当您这样做时,将通过延迟对象的承诺对象或延迟对象本身添加的任何回调函数都将被调用。您实际上之前见过这方面的一个例子,但我将在这里重复:
function doAsync3() { var deferredObject = $.Deferred(); setTimeout(function() { var randomValue = Math.random(); if(randomValue < 0.5) { deferredObject.resolve(); } else { deferredObject.reject(); } }, 1000); return deferredObject.promise(); }复制代码
可以看到,延迟对象的resolve()和reject()函数从传递给setTimeout()的延迟函数内部调用。 实际上,您可以将值传递给resolve()和reject()函数。然后,这些值将被传递给done()、fail()和always()函数注册的回调函数。 下面重写了doAsync4(),以说明如何通过resolve()和reject()传递参数:
function doAsync4() { var deferredObject = $.Deferred(); setTimeout(function() { var randomValue = Math.random(); if(randomValue < 0.5) { deferredObject.resolve( randomValue, "val2" ); } else { deferredObject.reject( randomValue, "errorCode" ); } }, 1000); return deferredObject; }复制代码
注册回调函数时,您应该让回调函数将传递的值作为参数。以下是一个例子:
doAsync4().done(function (value, value2) {console.log("doAsync4成功: " + value + " : " + value2);}).fail(function(value, value2) {console.log("doAsync4失败: " + value + " : " + value2);}).always(function(value, value2) {console.log(“始终4:”+值+“:”+值2);});复制代码
如您所见,这三个回调函数都使用两个参数。
实际上,您可以将值传递给resolve()和reject()函数。然后,这些值将被传递给done()、fail()和always()函数注册的回调函数。
监控延迟对象的进度
如果异步进程需要很长时间才能完成,您可以监控进程的进度。它要求异步进程将长期运行进程的进度通知潜在侦听器。这可以通过延迟对象notify()
函数完成。以下是一个演示如何调用notify()
函数的示例:
doAsync4().done(function (value, value2) { console.log("doAsync4 succeeded: " + value + " : " + value2); }).fail(function(value, value2) { console.log("doAsync4 failed: " + value + " : " + value2); }).always(function(value, value2) { console.log("always4: " + value + " : " + value2); });复制代码
注意doAsync5()
函数如何设置四个延迟函数。前3个延迟函数调用具有不同参数值的deferredObject.notify()
。最终延迟函数解析延迟对象。
在延迟对象上调用notify()
会导致调用通过关联的承诺对象上的progress()
函数添加的回调。以下是如何使用承诺对象上的progress()
函数来通知延迟对象上的notify()
调用:
doAsync5().progress(function(progressValue) {console.log(“doAsync5进度:”+ progressValue)}).done(function () {console.log(“doAsync5成功了。”);});复制代码
注意对doAsync5()返回的promise对象的progress()调用。传递给progress()的回调函数将在连接到promise对象的延迟对象上调用notify()函数时被调用。无论传递给notify()的是什么参数,都会被转发到progress()中注册的回调函数。
链化和过滤承诺对象
promise对象有一个叫做then()的函数,可以用来链接promise对象。下面是一个使用then()的链式承诺示例:
doAsync5().then(function(val) { console.log("done 1") }, function(val) { console.log("fail 1") }, function(val) { console.log("prog 1") } ).then(function(val) { console.log("done 2") }, function(val) { console.log("fail 2") }, function(val) { console.log("prog 2") } );复制代码
then()
函数需要三个参数。第一个参数是延迟对象解析(完成)时要调用的函数。第二个函数是当延迟对象被拒绝(失败)时要调用的函数。第三个函数是一个进度函数,每当异步代码在延迟对象上调用notify()
都会调用它。
上述示例中的代码与此代码相似:
doAsync5() .done(function(val) { console.log("done 1") }) .fail(function(val) { console.log("fail 1") }) .progress(function(val) { console.log("prog 1") }) .done(function(val) { console.log("done 2") }) .fail(function(val) { console.log("fail 2") }) .progress(function(val) { console.log("prog 2") }) ;复制代码
两个版本都设置了两组侦听器函数,用于监听延迟对象状态更改(已解决/被拒绝)和进度通知。不过,有一个显著的区别。
在最后显示的使用done()、fail()和progress()的版本中,传递给每个侦听器的值与传递给deferred对象的resolve()、reject()和notify()的值完全相同。
在使用then()的第一个版本中,每个侦听器函数都可以选择更改(过滤)传递给比自己晚注册的侦听器的值。它通过返回另一个值来实现。监听器返回的值将是返回给该类型下一个监听器的值。因此,解析的侦听器可以过滤传递给后续解析的侦听器的值。进度监听器可以过滤传递给后续进度监听器的值。对于被拒绝(失败)的听众也是一样。看看这个例子:
doAsync5().then(function(val) { console.log("done 1: " + val); return val + 1; },function(val) { console.log("fail 1: " + val) },function(val) { console.log("prog 1: " + val); return val + 1; }).then(function(val) { console.log("done 2: " + val) },function(val) { console.log("fail 2: " + val) },function(val) { console.log("prog 2: " + val) })复制代码
在本例中,第一个done
和progress
侦听器返回以1(val++
)递增传递给他们的原始值。返回的增量值将成为传递给同一类型下一个侦听器函数的参数值。这种过滤效果可用于创建高级承诺侦听器链。
将承诺与$.when()相结合
如果您需要等待多个延迟对象完成,您可以使用$.when()
函数组合他们的承诺对象。以下是一个例子:
$.when(doAsync4(), doAsync4()).done(function(val1, val2) {console.log(“val1:”+val1);console.log(“val2:”+val2);}).fail(function(val1, val2) {console.log("fail: " + val1 + " : " + val2 + " : " + val3);});复制代码
此示例两次调用doAsync4()
函数,并使用$.when()
函数组合两个函数调用返回的承诺对象。
when()函数返回一个新的promise对象,你可以使用done(),fail(),always()等来附加回调函数。如果所有作为参数传递给when()函数返回一个新的promise对象,你可以使用done(), fail(), always()等来附加回调函数。如果所有作为参数传递给when()函数返回一个新的promise对象,你可以使用done(),fail(),always()等来附加回调函数。如果所有作为参数传递给 when()调用的promise都成功(= have resolve()调用它们的延迟对象),那么通过done()对when()返回的promise设置的回调函数将被调用。如果传递给when()返回的promise设置的回调函数将被调用。如果传递给when()返回的promise设置的回调函数将被调用。如果传递给 when()的promise对象中有一个失败了,那么通过fail()添加的回调函数将被调用。因此,when()函数的工作方式类似于逻辑和函数。所有输入承诺必须成功,否则when()函数的工作方式类似于逻辑和函数。所有输入承诺必须成功,否则when()函数的工作方式类似于逻辑和函数。所有输入承诺必须成功,否则 when()返回的承诺也会成功。
在$ when()返回的promise上注册的回调函数的参数值与普通promise对象的参数的语义略有不同。在普通promise对象上,传递给done()回调的参数就是传递给deferred对象的resolve()函数的参数。但是,当您将两个promise对象组合在一起时,done()侦听器接收传递给两个延迟对象的resolve()调用的参数。
在组合resolve()参数时,需要考虑两个场景。第一个场景是所有对resolve()的调用都接受一个值。在这种情况下,传递给resolve()调用的每个值都作为单独的参数传递给通过done()函数在$ when()函数返回的promise对象上注册的回调函数。下面是在该场景中访问resolve()参数的示例:
$.when(doAsync4(), doAsync4()).done(function(val1, val2) {console.log(“val1:”+val1);console.log(“val2:”+val2);}).fail(function(val1, val2) {console.log("fail: " + val1 + " : " + val2 + " : " + val3);});复制代码
val1
参数将接收传递给$.when()
的第一个延迟对象的 resolve()
调用的值。val2
参数将接收传递给$.when()
的第二个延迟对象的 resolve()
调用的值。
如果一个或两个resolve()
调用收到多个参数值,则上面示例中val1
和val2
的含义会发生变化。不是参数值本身,而是来自第一个和第二个resolve()
调用的参数值数组。在这种情况下,您可以访问以下参数值:
$.when(doAsync4(), doAsync4()).done(function(val1, val2) {for(var i=0; i < val1.length; i++) {console.log("val1[" + i +"]: " + val1[i]);}for(var i=0; i < val2.length; i++) {console.log("val2[" + i +"]: " + val2[i]);}}).fail(function(val1, val2) {console.log("fail: " + val1 + " : " + val2 + " : " + val3);});复制代码
注意val1
和val2
现在是如何数组的。
如果其中一个延迟对象失败,则失败的延迟对象的reject()
调用的参数将传递给在$.when()
返回的承诺对象上使用fail()
注册的回调。无论一个或多个延迟对象是否失败,您只能从第一个失败的延迟对象的reject()
调用中获取参数,作为参数,以fail()
注册的回调由$.when()
返回的承诺。
与回调函数相比,递延对象优势
与将回调函数作为参数传递给异步函数相比,递延对象有一些优势。
与简单地接受单个回调函数的函数相比,附加多个回调函数的能力是延迟对象和承诺对象的优势之一。当然,异步函数可能只需要一个回调函数数组,但要等同于作用的承诺对象,每个异步函数都必须接受一系列“完成”回调、“失败”回调数组和“始终”回调数组。必须使用三个这样的数组并调用这些回调会导致笨拙的函数实现。将该功能集中在一个地方——延迟对象及其承诺对象——要干净得多。
除了在内部清理异步函数外,延迟对象还清理调用异步函数的外部代码。例如,$.when()
函数不必在回调中嵌套回调以等待多个异步操作完成,而是很好地结合了两个或多个承诺。
第三,一旦您理解了延迟对象并承诺对象,您几乎可以理解使用这些构造的所有函数。您了解如何处理返回值,如何处理故障,如果异步函数提供该值,则监控进度等。
jQuery中的递延对象
jQuery在其一些API中使用延迟对象和内部承诺。例如,当您调用$.ajax()
函数时,它会返回一个jqXHR
对象。jqXHR
对象上可用的函数包括三个承诺函数,done()
)、fail()
和always()
这意味着,即使jqXHR
对象不仅仅是一个承诺对象,您也可以将其用作承诺对象。
Promise对象也可以与jQuery中的动画一起使用。以下示例是示例jQuery文档的修改版本:
$( "#theButton" ).on( "click", function() { $( "div" ).each(function( i ) { $( this ).fadeIn().fadeOut( 1000 * ( i + 1 ) ); }); $( "div" ).promise().done(function() { $( "p" ).append( " Finished! " ); }); });复制代码
当带有idtheButton
按钮被单击时,所有div
元素都会被淡入。其次,从所有div
元素的集合中获取承诺对象。当所有选定的div
元素的动画完成后,此承诺对象将调用其done()
回调。从一系列元素中获得的承诺对象绑定到它们的动画中,这有点棘手的语义意义,我会同意的。
作者:Kimser
链接:https://juejin.cn/post/7026640914273533983