498

我一直在阅读有关 jQuery 延迟和承诺的信息,但我看不出使用.then()&.done()成功回调之间的区别。我知道Eric Hynds提到了这一点.done().success()映射到相同的功能,但我猜也是如此,.then()因为所有回调都是在成功操作完成时调用的。

谁能告诉我正确的用法?

4

11 回答 11

591

当 deferred 被解决时,附加的回调done()将被触发。当 deferred 被拒绝时,附加到的回调fail()将被触发。

在 jQuery 1.8 之前,then()只是语法糖:

promise.then( doneCallback, failCallback )
// was equivalent to
promise.done( doneCallback ).fail( failCallback )

从 1.8 开始,then()它是一个别名pipe()并返回一个新的 Promise 有关pipe().

success()并且error()仅在jqXHR调用返回的对象上可用ajax()。它们分别是done()和的简单别名fail()

jqXHR.done === jqXHR.success
jqXHR.fail === jqXHR.error

此外,done()不限于单个回调,并且会过滤掉非函数(尽管版本 1.8 中存在字符串错误,应在 1.8.1 中修复):

// this will add fn1 to 7 to the deferred's internal callback list
// (true, 56 and "omg" will be ignored)
promise.done( fn1, fn2, true, [ fn3, [ fn4, 56, fn5 ], "omg", fn6 ], fn7 );

也一样fail()

于 2011-03-25T18:55:38.270 回答
429

处理返回结果的方式也有所不同(称为链接,在产生调用链done时不链接)then

promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return 123;
}).then(function (x){
    console.log(x);
}).then(function (x){
    console.log(x)
})

将记录以下结果:

abc
123
undefined

尽管

promise.done(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return 123;
}).done(function (x){
    console.log(x);
}).done(function (x){
    console.log(x)
})

将得到以下信息:

abc
abc
abc

- - - - - 更新:

顺便提一句。我忘了说,如果你返回一个 Promise 而不是 atomic 类型的值,外层的 Promise 会等到内层的 Promise 解决:

promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return $http.get('/some/data').then(function (result) {
        console.log(result); // suppose result === "xyz"
        return result;
    });
}).then(function (result){
    console.log(result); // result === xyz
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

通过这种方式,组合并行或顺序异步操作变得非常简单,例如:

// Parallel http requests
promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);

    var promise1 = $http.get('/some/data?value=xyz').then(function (result) {
        console.log(result); // suppose result === "xyz"
        return result;
    });

    var promise2 = $http.get('/some/data?value=uvm').then(function (result) {
        console.log(result); // suppose result === "uvm"
        return result;
    });

    return promise1.then(function (result1) {
        return promise2.then(function (result2) {
           return { result1: result1, result2: result2; }
        });
    });
}).then(function (result){
    console.log(result); // result === { result1: 'xyz', result2: 'uvm' }
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

上面的代码并行发出两个 http 请求,从而使请求更快完成,而在这些 http 请求之下,则按顺序运行,从而减少了服务器负载

// Sequential http requests
promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);

    return $http.get('/some/data?value=xyz').then(function (result1) {
        console.log(result1); // suppose result1 === "xyz"
        return $http.get('/some/data?value=uvm').then(function (result2) {
            console.log(result2); // suppose result2 === "uvm"
            return { result1: result1, result2: result2; };
        });
    });
}).then(function (result){
    console.log(result); // result === { result1: 'xyz', result2: 'uvm' }
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})
于 2013-03-28T23:35:15.430 回答
60

.done()只有一个回调,它是成功回调

.then()有成功和失败回调

.fail()只有一个失败回调

所以你必须做什么取决于你……你关心它是成功还是失败?

于 2011-03-25T18:11:44.233 回答
16

deferred.done()

添加仅在解决 Deferred 时才调用的处理程序。您可以添加多个要调用的回调。

var url = 'http://jsonplaceholder.typicode.com/posts/1';
$.ajax(url).done(doneCallback);

function doneCallback(result) {
    console.log('Result 1 ' + result);
}

上面也可以这样写,

function ajaxCall() {
    var url = 'http://jsonplaceholder.typicode.com/posts/1';
    return $.ajax(url);
}

$.when(ajaxCall()).then(doneCallback, failCallback);

deferred.then()

添加要在 Deferred 被解决、拒绝或仍在进行时调用的处理程序。

var url = 'http://jsonplaceholder.typicode.com/posts/1';
$.ajax(url).then(doneCallback, failCallback);

function doneCallback(result) {
    console.log('Result ' + result);
}

function failCallback(result) {
    console.log('Result ' + result);
}
于 2015-03-15T03:42:50.710 回答
11

实际上有一个非常关键的区别,因为 jQuery 的 Deferreds 旨在成为 Promises 的实现(而 jQuery3.0 实际上试图将它们纳入规范)。

done/then 之间的主要区别在于

  • .done()总是返回与它开始时相同的 Promise/wrapped 值,无论您做什么或返回什么。
  • .then()总是返回一个新的 Promise,并且您负责根据您传递的函数返回的函数来控制该 Promise 的内容。

从 jQuery 翻译成原生 ES2015 Promises,.done()有点像在 Promise 链中围绕函数实现“tap”结构,如果链处于“resolve”状态,它将向函数传递一个值。 . 但该函数的结果不会影响链本身。

const doneWrap = fn => x => { fn(x); return x };

Promise.resolve(5)
       .then(doneWrap( x => x + 1))
       .then(doneWrap(console.log.bind(console)));

$.Deferred().resolve(5)
            .done(x => x + 1)
            .done(console.log.bind(console));

这些都将记录 5,而不是 6。

请注意,我使用 done 和 doneWrap 进行日志记录,而不是 .then。那是因为 console.log 函数实际上并没有返回任何东西。如果你传递 .then 一个不返回任何东西的函数会发生什么?

Promise.resolve(5)
       .then(doneWrap( x => x + 1))
       .then(console.log.bind(console))
       .then(console.log.bind(console));

这将记录:

5

不明确的

发生了什么?当我使用 .then 并传递给它一个不返回任何内容的函数时,它的隐含结果是“未定义”......当然它返回了一个 Promise[undefined] 到下一个 then 方法,该方法记录未定义。所以我们一开始的原始价值基本丢失了。

.then()本质上是函数组合的一种形式:每一步的结果被用作下一步函数的参数。这就是为什么 .done 最好被认为是一个“点击”->它实际上不是组合的一部分,只是在某个步骤偷看值并在该值处运行一个函数,但实际上并没有改变以任何方式组成。

这是一个非常根本的区别,并且可能有一个很好的理由说明原生 Promises 没有自己实现 .done 方法。我们不必讨论为什么没有 .fail 方法,因为那更复杂(即 .fail/.catch 不是 .done/.then 的镜像 -> .catch 中返回裸值的函数不会“留下”像那些传递给的那样被拒绝。然后,他们解决了!)

于 2015-12-22T19:02:15.507 回答
7

then()总是意味着它会在任何情况下被调用。但是在不同的jQuery版本中传递的参数是不同的。

在 jQuery 1.8 之前,then()等于done().fail(). 并且所有的回调函数共享相同的参数。

但是从 jQuery 1.8 开始,then()返回一个新的 Promise,如果它有返回值,它将被传递给下一个回调函数。

让我们看看下面的例子:

var defer = jQuery.Deferred();

defer.done(function(a, b){
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
});

defer.resolve( 3, 4 );

在 jQuery 1.8 之前,答案应该是

result = 3
result = 3
result = 3

全部result取 3。then()函数总是将相同的延迟对象传递给下一个函数。

但从 jQuery 1.8 开始,结果应该是:

result = 3
result = 7
result = NaN

因为第一个then()函数返回一个新的 promise,并且值 7(这是唯一会传递的参数)被传递给 next done(),所以第二个done()write result = 7。第二个then()以 7 作为 的值aundefined作为 的值b,因此第二个then()返回一个带有参数 NaN 的新承诺,最后一个done()打印 NaN 作为其结果。

于 2015-07-06T13:28:08.747 回答
4

只使用.then()

这些都是缺点.done()

  • 不能上链
  • resolve()调用(所有.done()处理程序将同步执行)
  • resolve()可能会从注册的.done()处理程序(!)
  • 一个.done()半杀的例外被推迟:
    • 进一步.done()的处理程序将被静默跳过

我暂时认为,.then(oneArgOnly)总是需要.catch()这样才能无声地忽略任何异常,但这不再是真的:事件在控制台上unhandledrejection记录未处理的异常(默认情况下)。.then()很合理!根本没有理由使用.done()

证明

以下代码片段显示:

  • 所有.done()处理程序将在点同步调用resolve()
    • 记录为 1、3、5、7
    • 在脚本落入底部之前记录
  • .done()影响resolve()调用者 的异常
    • 通过追赶记录resolve()
  • 异常破坏了进一步.done()解决 的承诺
    • 8 和 10 未记录!
  • .then()没有这些问题
    • 线程空闲后记录为 2、4、6、9、11
    • (片段环境unhandledrejection似乎没有)

顺便说一句,.done()无法正确捕获来自的异常:由于 的同步模式.done(),错误要么在.resolve()(可能是库代码!)处抛出,要么在.done()附加了罪魁祸首的调用处抛出,如果延迟已经解决。

console.log('Start of script.');
let deferred = $.Deferred();
// deferred.resolve('Redemption.');
deferred.fail(() => console.log('fail()'));
deferred.catch(()=> console.log('catch()'));
deferred.done(() => console.log('1-done()'));
deferred.then(() => console.log('2-then()'));
deferred.done(() => console.log('3-done()'));
deferred.then(() =>{console.log('4-then()-throw');
    throw 'thrown from 4-then()';});
deferred.done(() => console.log('5-done()'));
deferred.then(() => console.log('6-then()'));
deferred.done(() =>{console.log('7-done()-throw');
    throw 'thrown from 7-done()';});
deferred.done(() => console.log('8-done()'));
deferred.then(() => console.log('9-then()'));

console.log('Resolving.');
try {
    deferred.resolve('Solution.');
} catch(e) {
    console.log(`Caught exception from handler
        in resolve():`, e);
}
deferred.done(() => console.log('10-done()'));
deferred.then(() => console.log('11-then()'));
console.log('End of script.');
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha384-vk5WoKIaW/vJyUAd9n/wmopsmNhiy+L2Z+SBxGYnUkunIxVxAv/UtMOhba/xskxh"
crossorigin="anonymous"
></script>

于 2020-04-01T04:40:51.260 回答
3

从jQuery 3.0开始,还有一个重要的区别很容易导致意外行为,并且在之前的答案中没有提到:

考虑以下代码:

let d = $.Deferred();
d.done(() => console.log('then'));
d.resolve();
console.log('now');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

这将输出:

then
now

现在,在相同的代码段中替换done()为:then()

var d = $.Deferred();
d.then(() => console.log('then'));
d.resolve();
console.log('now');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

现在的输出是:

now
then

因此,对于立即解决的延迟,传递给的函数done()将始终以同步方式调用,而传递给的任何参数then()都是异步调用的。

这与升级指南中提到的两个回调同步调用的先前 jQuery 版本不同:

Promises/A+ 合规性所需的另一个行为更改是延迟的 .then() 回调始终被异步调用。以前,如果将 .then() 回调添加到已解决或拒绝的 Deferred 中,则回调将立即同步运行。

于 2019-12-04T14:58:20.420 回答
3

有一个非常简单的心理映射作为回应,在其他答案中有点难以找到:

于 2017-12-08T16:24:37.137 回答
1

除了上面的答案:

.then 的真正强大之处在于可以流畅地链接 ajax 调用,从而避免回调地狱。

例如:

$.getJSON( 'dataservice/General', {action:'getSessionUser'} )
    .then( function( user ) {
        console.log( user );
        return $.getJSON( 'dataservice/Address', {action:'getFirstAddress'} );
    })
    .then( function( address ) {
        console.log( address );
    })

这里第二个 .then 跟随返回的 $.getJSON

于 2021-02-17T19:12:18.567 回答
-4

.done()终止承诺链,确保没有其他东西可以附加进一步的步骤。这意味着 jQuery Promise 实现可以抛出任何未处理的异常,因为没有人可以使用.fail().

实际上,如果您不打算在 promise 上附加更多步骤,则应该使用.done(). 有关更多详细信息,请参阅为什么需要完成承诺

于 2014-10-30T14:27:40.637 回答