jQuery的延迟和承诺-.then()与.done()


473

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

有人可以启发我正确使用吗?


15
请注意每个人,2016年6月发布的JQuery 3.0是第一个符合Promises / A +和ES2015 Promises规范的版本。在此之前的实现与应有的承诺不兼容。
Flimm

我更新了答案,并提出了有关何时使用的改进建议。
罗伯·西默

Answers:


577

done()延迟解决后,将触发附加到的回调。附加到的回调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()


8
then兑现新的承诺是我缺少的关键。我不明白为什么像这样的链$.get(....).done(function(data1) { return $.get(...) }).done(function(data2) { ... })data2未定义而失败;当我改用它时donethen它是有效的,因为我真的很想将诺言发送到一起,而不是将更多的处理程序附加到原始诺言上。
wrschneider 2015年

5
jQuery 3.0是第一个符合Promises / A +和ES2015规范的版本。
Flimm

4
我仍然不明白为什么我要在另一个上使用一个。如果我进行了ajax调用,而我需要等到该调用完全完成(这意味着从服务器返回响应)之后再调用另一个ajax调用,我该使用done还是then?为什么?
CodingYoshi

@CodingYoshi查看我的答案以最终回答该问题(使用.then())。
罗伯·西默

413

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

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而不是原子类型值,则外部诺言将等到内部诺言得到解决:

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

121
+1表示donethen更改结果的结果。其他imo错过了巨大的机会。
Shanimal

9
值得一提的是,这适用于什么版本的jQuery,因为行为then在1.8 中已更改
bradley.ayers 2013年

4
+1直达目标。我创建了一个可运行的例子,如果有人想看到什么链混合donethen在调用的结果。
迈克尔Kropat

7
上面的示例还强调了“完成”对最初创建的原始承诺对象有效,但“然后”返回了新的承诺。
Pulak Kanti Bhattacharyya 2014年

2
这适用于jQuery 1.8+。旧版本的行为与done示例相同。更改thenpipe1.8之前的版本即可获得1.8+的then行为。
David Harkness 2014年

57

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

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

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

因此,由您决定您必须做什么...是否在乎它是否成功?


18
您没有提及“ then”产生呼叫链。参见Lu4的答案。
oligofren

您的答案是从2011年开始...如今,它们的返回值与then()十分不同done()。正如then()通常在成功回调中通常所说的那样,您的意思是一个细节,而不是要记住/知道的主要内容。(不能说jQuery 3.0之前的情况。)
Robert Siemer

14

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

then如果没有fail提供回调,您的帖子将不清楚行为-即根本没有捕捉到这种fail情况
BM

失败案例会引发程序顶层可能捕获的异常。您还可以在JavaScript控制台中看到异常。
David Spector

10

实际上,就jQuery的Deferreds而言,它是Promises的实现(而jQuery3.0实际上试图将它们纳入规范),实际上存在一个非常关键的区别。

完成/然后之间的主要区别是

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

从jQuery转换为本地ES2015 Promises,.done()有点像在Promise链中围绕函数实现“敲击”结构,因为如果链处于“解决”状态,它将向函数传递值。但是,该函数的结果不会影响链本身。

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中返回裸值的函数不会像传递给.the的那些人一样拒绝了“ stay”,然后他们解决了!)


6

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(这是将传递的唯一参数)被传递给下一个done(),所以第二个done()write result = 7。第二个then()取值为7,a而取值undefinedb,因此第二个then()返回参数为NaN的新诺言,最后一个done()输出NaN作为结果。


“ then()始终意味着在任何情况下都将调用它” –不正确。如果Promise内部发生错误,则永远不会调用then()。
David Spector

有趣的方面是,a jQuery.Deferred()可以接收多个值,并将其正确地传递到第一个值.then()。尽管有点奇怪,但是任何后续代码.then()都不能这样做。(通过选择的接口return只能返回一个值。)Javascript的本机Promise不执行该操作。(老实说,这是更一致的。)
Robert Siemer


2

只用 .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>


有几件事:1)我明白您的意思是,done如果先前完成的操作有异常,则不会执行。但是为什么会默默地忽略它,我的意思是发生了异常,所以为什么您说它是默默的。2)我鄙视该Deferred对象,因为它的API做得非常差。这太复杂和令人困惑。您的代码在这里既无助于证明您的观点,也为您要证明的内容带来了不必要的复杂性。3)为什么done在第二个索引之前执行at索引2、4和6 then
CodingYoshi

我的糟糕,您绝对值得投票。至于您对异常的评论,通常就是异常的工作方式:一旦引发,之后的代码将不会执行。加上jQuery文档指出,只有在延迟解决后,它才会执行。
CodingYoshi

@CodingYoshi这里的情况有所不同:我只是在谈论已解决的承诺/延期。我不是在抱怨没有成功处理程序的其余部分,这很正常。但是我认为没有理由不要求一个完全不同的成功承诺者。.then()无论是否引发异常(在这些处理程序中),都将被调用。但是增加/剩余.done()中断。
罗伯·西默

@CodingYoshi,如果允许我说的话,我的回答大大改善了。代码和文字。
罗伯·西默

1

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 +要求的另一个行为更改是Deferred .then()回调始终被异步调用。以前,如果将.then()回调添加到已解决或拒绝的Deferred中,则该回调将立即并同步运行。


-5

.done()终止承诺链,确保没有其他事情可以采取进一步措施。这意味着jQuery promise实现可以抛出任何未处理的异常,因为没有人可以使用来处理它.fail()

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


6
警告!对于几个promise实现,此答案将是正确的,但对于jQuery .done()没有终结作用的jQuery,则不是。该文档说:“由于deferred.done()返回延迟的对象,因此可以将延迟对象的其他方法链接到该对象,包括其他.done()方法。” .fail()没有提及,但是,可以,也可以将其链接起来。
Roamer-1888

1
我不好,没有检查jQuery
gleb bahmutov

1
@glebbahmutov-也许您应该删除此答案,以免其他人感到困惑?只是一个友好的建议:)
Andrey

2
请不要删除答案,这也可以帮助人们消除误解。
梅利莎(Melissa)2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.