我什么时候应该使用延迟的 jQuery 的“ then”方法,什么时候应该使用“ tube”方法?

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

then()

deferred.then( doneCallbacks, failCallbacks ) Returns: Deferred

解析 Deferred 时调用的函数或函数数组。
当延迟被拒绝时调用的函数或函数数组。

pipe()

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

DoneFilter 解析 Deferred 时调用的可选函数。
FailFilter 在拒绝延迟时调用的可选函数。

我知道 then()pipe()存在的时间要长一些,所以后者必须增加一些额外的好处,但是差别究竟是什么使我迷惑不解。两个回调参数几乎相同,尽管名称不同,返回 Deferred和返回 Promise之间的差异似乎很小。

我一遍又一遍地阅读官方文档,但总是发现它们太“密集”,以至于我无法真正理解它们。我在搜索中发现了很多关于这个或那个功能的讨论,但是我没有发现任何东西能够真正澄清每个功能的优缺点。

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


加法

Felix 出色的回答 确实有助于阐明这两种功能的区别。但是我想知道是否有时候 then()的功能比 pipe()的更好。

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

但是,是否有一个用例需要 then()返回原来的 Deferred,而 pipe()不能这样做,因为它返回一个新的 Promise

40495 次浏览

Since jQuery 1.8 .then behaves the same as .pipe:

Deprecation Notice: As of jQuery 1.8, the deferred.pipe() method is deprecated. The deferred.then() method, which replaces it, should be used instead.

and

As of jQuery 1.8, the deferred.then() method returns a new promise that can filter the status and values of a deferred through a function, replacing the now-deprecated deferred.pipe() method.

The examples below might still be helpful to some.


They serve different purposes:

  • .then() is to be used whenever you want to work with the result of the process, i.e. as the documentation says, when the deferred object is resolved or rejected. It is the same as using .done() or .fail().

  • You'd use .pipe() to (pre)filter the result somehow. The return value of a callback to .pipe() will be passed as argument to the done and fail callbacks. It can also return another deferred object and the following callbacks will be registered on this deferred.

    That is not the case with .then() (or .done(), .fail()), the return values of the registered callbacks are just ignored.

So it is not that you use either .then() or .pipe(). You could use .pipe() for the same purposes as .then() but the converse does not hold.


Example 1

The result of some operation is an array of objects:

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

and you want to compute the minimum and maximum of the values. Lets assume we use two done callbacks:

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" */


});

In both cases you have to iterate over the list and extract the value from each object.

Wouldn't it be better to somehow extract the values beforehand so that you don't have to do this in both callbacks individually? Yes! And that's what we can use .pipe() for:

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" */


});

Obviously this is a made up example and there are many different (maybe better) ways to solve this problem, but I hope it illustrates the point.


Example 2

Consider Ajax calls. Sometimes you want to initiate one Ajax call after a previous one completes. One way is to make the second call inside a done callback:

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

Now lets assume you want to decouple your code and put these two Ajax calls inside a function:

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
});
});
}

You'd like to use the deferred object to allow other code which calls makeCalls to attach callbacks for the second Ajax call, but

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

would not have the desired effect as the second call is made inside a done callback and not accessible from the outside.

The solution would be to use .pipe() instead:

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
});

By using .pipe() you can now make it possible to append callbacks to the "inner" Ajax call without exposing the actual flow/order of the calls.


In general, deferred objects provide an interesting way to decouple your code :)

There is no case where you MUST use then() over pipe(). You can always choose to ignore the value that pipe() will pass in. There might be a slight performance hit for using pipe -- but it is unlikely to matter.

So it might seem like you could simply always use pipe() in both cases. However, by using pipe(), you are communicating to other people reading your code (including yourself, six months from now) that there is some importance to the return value. If you're discarding it, you're violating this semantic construct.

It's like having a function that returns a value that is never used: confusing.

So use then() when you should, and pipe() when you should...

In fact it turns out that the difference between .then() and .pipe() has been deemed unnecessary and they have been made to be the same as of jQuery version 1.8.

From a comment by jaubourg in jQuery's bug tracker ticket #11010 "MAKE DEFERRED.THEN == DEFERRED.PIPE LIKE PROMISE/A":

In 1.8, we will remove the old then and replace it with current pipe. But the very sadening consequence is that we'll have to tell people to use the non-standard done, fail and progress, because the proposal doesn't provide simple, EFFICIENT, mean to just add a callback.

(emphassis mine)