我应该在什么时候使用jQuery deferred的"then"方法,以及在什么时候使用"pipe"方法?

98

jQuery的Deferred有两个函数可用于实现异步函数的链式调用:

then()

deferred.then( doneCallbacks, failCallbacks ) 返回:Deferred

doneCallbacks 当Deferred被解决时调用的函数或函数数组。
failCallbacks 当Deferred被拒绝时调用的函数或函数数组。

pipe()

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

doneFilter 可选函数,当Deferred被解决时调用。
failFilter 可选函数,当Deferred被拒绝时调用。

我知道then()的存在比pipe()久一些,所以后者必须添加一些额外的优势,但是它们之间的区别确切地是什么我不太清楚。虽然它们在名称上有所不同,但它们基本上采用相同的回调参数,并且返回Deferred和返回Promise之间的区别似乎很小。
我一遍又一遍地阅读了官方文档,但总觉得它们过于“密集”,无法真正理解,并且搜索发现有很多关于其中一个功能或另一个功能的讨论,但我没有找到任何真正澄清每个方法的不同优缺点的内容。 那么什么时候更好地使用then,什么时候更好地使用pipe

加法

Felix的优秀答案真正帮助澄清了这两个函数的区别。但我想知道是否有时候then()的功能比pipe()更可取。

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

但是否有一种用例需要then()返回原始Deferred而无法使用pipe()返回新的Promise


1
我思考了一段时间,但说实话,我想不出任何用例。如果您不需要它们(我不知道它们在内部如何链接),创建新的Promise对象可能只是一种开销。尽管如此,肯定有人比我更好地理解这个问题。 - Felix Kling
6
对于此问题感兴趣的人一定会对 jQuery bug 跟踪器上的 Ticket #11010 感兴趣:**MAKE DEFERRED.THEN == DEFERRED.PIPE LIKE PROMISE/A**。 - hippietrail
3个回答

106
自从jQuery 1.8版本以后,.then()方法的行为与.pipe()方法相同:

废弃通知:自jQuery 1.8版本起,deferred.pipe()方法已被弃用。应使用替代它的deferred.then()方法。


并且


自jQuery 1.8版本起deferred.then()方法返回一个新的promise。可以通过函数过滤deferred对象的状态和值,替代现在已过时的deferred.pipe()方法。


以下示例对某些人仍然有帮助。


它们有不同的用途:


  • .then()适用于您想要使用过程结果的情况,例如当deferred对象被解析或拒绝时。这与使用.done().fail()相同。

  • 您可以使用.pipe()来(预)过滤结果。回调函数的返回值将作为参数传递给donefail回调函数。它也可以返回另一个deferred对象,接下来的回调函数将在该deferred对象上注册。

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

因此,并不是您只使用.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调用。有时您希望在先前的调用完成后启动第二个调用。一种方法是在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; 吗?) - hippietrail
当我说我不理解你的例子时,是不是像这样:在上面的例子中,两个.then()方法都接收到相同的数据result,你每次都对其进行过滤;而在下面的例子中,.pipe()方法在将一些数据从其result中删除后,将其作为后续的两个.then()方法将接收到的result传递给它们? - hippietrail
1
@hippietrail:与此同时,我更新了我的答案,并包括了.pipe()的其他用途。如果回调函数返回一个延迟对象,则随后的done或fail回调将为该对象注册。我将包括另一个示例。编辑:关于您的第二个评论:是的。 - Felix Kling
我刚意识到我显然混淆了then()pipe()的返回值与它们(匿名)回调函数的返回值... - hippietrail
1
没问题 :) 我也花了一些时间才完全理解延迟对象及其方法的工作原理。但是一旦你理解了它,它就不再难了。我同意文档可能可以用更简单的方式编写。 - Felix Kling
显示剩余5条评论

7
没有一种情况是必须使用then()而不是pipe()。您总是可以选择忽略pipe()传递的值。对于使用pipe()可能会稍微减少一些性能,但这不太重要。
因此,似乎在这两种情况下都可以简单地使用pipe()。然而,通过使用pipe(),您正在向其他人(包括未来的自己)传达某个返回值的重要性。如果您将其丢弃,则违反了此语义构造。
这就像有一个从未被使用的返回值的函数:令人困惑。
所以,在应该使用then()时,请使用它,在应该使用pipe()时,请使用它...

3
我在K. Scott Allen的博客“Experiments In Writing”中找到了一个真实的例子,说明了如何同时使用地理定位、地理编码和jQuery Promises。点击链接:Geolocation, Geocoding, and jQuery Promises。然后,控制逻辑就很容易读懂了:$(function () { $.when(getPosition()) .pipe(lookupCountry) .then(displayResults); });请注意,pipethen不同,因为pipe返回一个新的Promise。 - hippietrail

5
事实上,.then()和.pipe()之间的差异被认为是不必要的,并且在jQuery 1.8版中已经被作为相同的函数。来自于jQuery错误跟踪器中jaubourg的评论票号#11010:“MAKE DEFERRED.THEN == DEFERRED.PIPE LIKE PROMISE / A”:
在1.8中,我们将删除旧的.then()并将其替换为当前的.pipe()。但令人非常遗憾的后果是我们将不得不告诉人们使用非标准的done,fail和progress,因为该提案没有提供简单,有效的方法来添加回调。 (重点是我的)

1
到目前为止,这是最好的参考资料,我正在寻找高级用法。 - TWiStErRob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接