什么时候应该使用 jQuery deferred 的“then”方法,什么时候应该使用“pipe”方法?

Posted

技术标签:

【中文标题】什么时候应该使用 jQuery deferred 的“then”方法,什么时候应该使用“pipe”方法?【英文标题】:When should I use jQuery deferred's "then" method and when should I use the "pipe" method? 【发布时间】:2012-03-23 22:28:21 【问题描述】:

jQuery 的Deferred 有两个函数可以用来实现函数的异步链接:

then()

deferred.then( doneCallbacks, failCallbacks ) Returns: Deferred

doneCallbacks 一个函数或函数数组,在 Deferred 解决时调用。failCallbacks 一个函数或函数数组,在 Deferred 解决时调用被拒绝。

pipe()

deferred.pipe( [doneFilter] [, failFilter] ) Returns: Promise

doneFilter 解析 Deferred 时调用的可选函数。failFilter 拒绝 Deferred 时调用的可选函数。

我知道then() 的存在时间比pipe() 长一点,所以后者必须增加一些额外的好处,但我不知道具体的区别是什么。两者都采用几乎相同的回调参数,尽管它们的名称不同,并且返回 Deferred 和返回 Promise 之间的差异似乎很小。

我一遍又一遍地阅读官方文档,但总是觉得它们太“密集”而无法真正包裹我的头,搜索发现很多关于一个或另一个特性的讨论,但我没有找到任何真正阐明了每种方法的不同优缺点。

那么什么时候使用then更好,什么时候使用pipe更好?


加法

Felix's excellent answer 确实帮助阐明了这两个功能的不同之处。但我想知道then() 的功能是否有时优于pipe() 的功能。

显然pipe()then() 更强大,而且似乎前者可以做后者可以做的任何事情。使用then() 的一个原因可能是它的名称反映了它作为处理相同数据的一系列函数的终止角色。

但是否存在需要 then() 返回原始 Deferred 的用例,而 pipe() 由于返回新的 Promise 而无法完成?

【问题讨论】:

我想了一会儿,但是tbh,我想不出任何用例。如果您不需要它们,创建新的 Promise 对象可能只是一种开销(我不知道它们是如何在内部链接在一起的)。也就是说,肯定有人比我更了解这一点。 任何对此问题感兴趣的人肯定会对 jQuery 错误跟踪器上的 Ticket #11010 感兴趣:MAKE DEFERRED.THEN == DEFERRED.PIPE LIKE PROMISE/A 相关:pipe() and then() documentation vs reality in jQuery 1.8 【参考方案1】:

由于 jQuery 1.8 .then 的行为与 .pipe 相同:

弃用通知: 从 jQuery 1.8 开始,deferred.pipe() 方法已弃用。应该使用替换它的deferred.then() 方法。

从 jQuery 1.8 开始deferred.then() 方法返回一个新的 Promise,它可以通过函数过滤一个 deferred 的状态和值,替换现在已弃用的 deferred.pipe() 方法。

下面的例子可能对某些人仍然有帮助。


它们有不同的用途:

.then() 将在您想要处理过程的结果时使用,即如文档所述,当延迟对象被解决或拒绝时。与使用.done().fail() 相同。

您会以某种方式使用.pipe() 来(预)过滤结果。对.pipe() 的回调的返回值将作为参数传递给donefail 回调。它还可以返回另一个deferred对象,并且会在这个deferred上注册以下回调。

.then()(或.done().fail())不是这样,注册回调的返回值会被忽略。

所以不是你使用 either .then() .pipe()。您可以.pipe() 用于与.then() 相同的目的,但反过来不成立。


示例 1

一些操作的结果是一个对象数组:

[value: 2, value: 4, value: 6]

并且您想要计算值的最小值和最大值。假设我们使用两个 done 回调:

deferred.then(function(result) 
    // result = [value: 2, value: 4, value: 6]

    var values = [];
    for(var i = 0, len = result.length; i < len; i++) 
        values.push(result[i].value);
    
    var min = Math.min.apply(Math, values);

   /* do something with "min" */

).then(function(result) 
    // result = [value: 2, value: 4, value: 6]

    var values = [];
    for(var i = 0, len = result.length; i < len; i++) 
        values.push(result[i].value);
    
    var max = Math.max.apply(Math, values);

   /* do something with "max" */ 

);

在这两种情况下,您都必须遍历列表并从每个对象中提取值。

事先以某种方式提取值,这样您就不必在两个回调中单独执行此操作不是更好吗?是的!这就是我们可以使用.pipe() 的目的:

deferred.pipe(function(result) 
    // result = [value: 2, value: 4, value: 6]

    var values = [];
    for(var i = 0, len = result.length; i < len; i++) 
        values.push(result[i].value);
    
    return values; // [2, 4, 6]

).then(function(result) 
    // result = [2, 4, 6]

    var min = Math.min.apply(Math, result);

    /* do something with "min" */

).then(function(result) 
    // result = [2, 4, 6]

    var max = Math.max.apply(Math, result);

    /* do something with "max" */

);

显然这是一个虚构的例子,有很多不同的(也许更好的)方法可以解决这个问题,但我希望它能说明这一点。


示例 2

考虑 Ajax 调用。有时您想在前一个 Ajax 调用完成后启动一个 Ajax 调用。一种方法是在 done 回调中进行第二次调用:

$.ajax(...).done(function() 
    // executed after first Ajax
    $.ajax(...).done(function() 
        // executed after second call
    );
);

现在假设您想解耦代码并将这两个 Ajax 调用放在一个函数中:

function makeCalls() 
    // here we return the return value of `$.ajax().done()`, which
    // is the same deferred object as returned by `$.ajax()` alone

    return $.ajax(...).done(function() 
        // executed after first call
        $.ajax(...).done(function() 
            // executed after second call
        );
    );

您希望使用延迟对象来允许调用makeCalls 的其他代码附加回调以进行第二次 Ajax 调用,但是

makeCalls().done(function() 
    // this is executed after the first Ajax call
);

不会产生预期的效果,因为第二次调用是在 done 回调中进行的,并且无法从外部访问。

解决方案是改用.pipe()

function makeCalls() 
    // here we return the return value of `$.ajax().pipe()`, which is
    // a new deferred/promise object and connected to the one returned
    // by the callback passed to `pipe`

    return $.ajax(...).pipe(function() 
        // executed after first call
        return $.ajax(...).done(function() 
            // executed after second call
        );
    );


makeCalls().done(function() 
    // this is executed after the second Ajax call
);

通过使用.pipe(),您现在可以将回调附加到“内部”Ajax 调用,而不会暴露调用的实际流程/顺序。


一般来说,延迟对象提供了一种有趣的方式来解耦你的代码:)

【讨论】:

啊,是的,我忽略了pipe 可以进行then 无法进行的过滤。但在谷歌搜索这些主题时,他们似乎选择称它为pipe 而不是filter,因为认为过滤是它附带的额外奖励,而pipe 更清楚地表明了它的真正目的。所以似乎除了过滤之外应该还有其他差异。 (然后我再次承认,即使是您的示例,我也不太了解过滤功能。顺便说一下,result values; 应该是return values; 吗?) 当我说我看不懂你的例子时,是不是这样:在上面的例子中,两个.then()s在result中接收到相同的数据,你每次过滤;而在下面的示例中,.pipe() 删除了其result 中的一些数据,然后将其传递给result,随后的两个.then()s 将收到? @hippietrail:我同时更新了我的答案,还包括了.pipe() 的其他用途。如果回调返回一个延迟对象,后续的完成或失败回调将为该对象注册。我将包括另一个例子。 编辑: 关于您的第二条评论:是的。 所以一个区别是数据流通过pipe()then()更像是一个叶节点,你必须使用您的数据,它不会进一步流动,尽管then() 返回Deferred 它实际上并没有使用/有用?如果这是正确的,则可能有助于澄清在每个“.then() 子句”中包含类似 /* do something with "min"/"max" */ 的内容。 不用担心 :) 我也花了一些时间来完全理解延迟对象及其方法的工作原理。但是一旦你理解了它,它似乎不再是困难的了。我同意文档可能会以更简单的方式编写。【参考方案2】:

在任何情况下,您都必须使用 then() 而不是 pipe()。您始终可以选择忽略pipe() 将传入的值。可能使用pipe 会对性能造成轻微影响——但这不太重要。

所以看起来你可以简单地在这两种情况下都使用pipe()但是,通过使用pipe(),您正在与阅读您的代码的其他人(包括您自己,从现在起六个月后)交流返回值有一些重要性。如果你丢弃它,你就违反了这个语义结构。

这就像有一个函数返回一个从未使用过的值:令人困惑。

所以在你应该使用then() 的时候使用pipe() 在你应该使用的时候......

【讨论】:

我在 K. Scott Allen 的博客“写作实验”中找到了一个使用这两个的真实示例:Geolocation, Geocoding, and jQuery Promises:“然后控制逻辑读取很好:" $(function () $.when(getPosition()) .pipe(lookupCountry) .then(displayResults); ); "请注意,管道与当时不同,因为管道返回了一个新的承诺。"【参考方案3】:

事实上,.then().pipe() 之间的区别被认为是不必要的,它们已被制作为与 jQuery 1.8 版相同。

来自 jQuery 的错误跟踪器 ticket #11010 中的 a comment by jaubourg "MAKE DEFERRED.THEN == DEFERRED.PIPE LIKE PROMISE/A":

在 1.8 中,我们将删除旧的 then 并用当前的管道替换它。 但非常可悲的后果是我们将不得不告诉人们使用非标准的 done、fail 和进度,因为提案没有提供简单、高效的意思,只是添加一个回调。

(强调我的)

【讨论】:

目前最好的参考,我正在寻找高级用法。

以上是关于什么时候应该使用 jQuery deferred 的“then”方法,什么时候应该使用“pipe”方法?的主要内容,如果未能解决你的问题,请参考以下文章

jquery deferred对象

jQuery的deferred

jQuery的deferred对象详解

jQuery的deferred对象详解

jQuery的deferred对象详解

jQuery的deferred对象详解