【问题标题】:jQuery deferreds and promises - .then() vs .done()jQuery 延迟和承诺 - .then() 与 .done()
【发布时间】:2011-07-23 02:54:36
【问题描述】:

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

谁能告诉我正确的用法?

【问题讨论】:

  • 请注意,2016 年 6 月发布的 JQuery 3.0 是第一个符合 Promises/A+ 和 ES2015 Promises 规范的版本。之前的实现与应该提供的承诺不兼容。
  • 我更新了my answer,改进了何时使用的建议。

标签: jquery promise jquery-deferred


【解决方案1】:

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

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

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

从 1.8 开始,then()pipe() 的别名并返回一个新的承诺,有关 pipe() 的更多信息,请参阅 here

success()error() 仅在调用ajax() 返回的jqXHR 对象上可用。它们分别是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() 也是如此。

【讨论】:

  • then 返回一个新的承诺是我错过的关键事情。我不明白为什么像$.get(....).done(function(data1) { return $.get(...) }).done(function(data2) { ... }) 这样的链因data2 undefined 而失败;当我将 done 更改为 then 时,它起作用了,因为我真的想将 Promise 连接在一起,而不是在原始 Promise 上附加更多的处理程序。
  • jQuery 3.0 是第一个符合 Promises/A+ 和 ES2015 规范的版本。
  • 我仍然不明白为什么我会使用其中一个。如果我进行了一个 ajax 调用,并且我需要等到该调用完全完成(意味着从服务器返回响应),然后再调用另一个 ajax 调用,我应该使用done 还是then?为什么?
  • @CodingYoshi 查看my answer 以最终回答该问题(使用.then())。
【解决方案2】:

返回结果的处理方式也有所不同(称为链接,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
})

【讨论】:

  • +1 表示done 对结果没有任何作用,而then 会改变结果。其他人错过了重要的一点。
  • 可能值得一提的是它适用于哪个版本的 jQuery,因为 then 的行为在 1.8 中发生了变化
  • +1 开门见山。我创建了一个runnable example,如果有人想查看混合donethen 调用的链会产生什么结果。
  • 上面的例子还强调了'done'作用于最初创建的原始promise对象,但'then'返回一个新的promise。
  • 这适用于 jQuery 1.8+。旧版本的行为就像done 示例一样。在 pre-1.8 中将 then 更改为 pipe 以获得 1.8+ then 行为。
【解决方案3】:

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

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

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

所以你必须做什么由你决定......你关心它是成功还是失败?

【讨论】:

  • 你没有提到'then'会产生调用链。见 Lu4 的回答。
  • 您的答案来自 2011 年...如今它们的返回值使 then()done() 大不相同。由于then() 通常仅在成功回调时才被调用,因此您的观点是一个细节,而不是要记住/知道的主要内容。 (不能说在 jQuery 3.0 之前是怎样的。)
【解决方案4】:

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

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

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

【讨论】:

  • 如果没有提供 fail 回调,您的帖子并不清楚 then 的行为 - 即根本不捕获 fail 案例
  • 失败案例引发异常,可以被程序的顶层捕获。您还可以在 JavaScript 控制台中看到异常。
【解决方案5】:

实际上有一个非常关键的区别,因为 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 中返回裸值的函数不会“留下”像那些传递给的那样被拒绝。然后,他们解决了!)

【讨论】:

    【解决方案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()写成result = 7。第二个then()取7作为a的值,取undefined作为b的值,所以第二个then()返回一个带有参数NaN的新promise,最后一个done()打印NaN结果。

    【讨论】:

    • "then() 总是意味着它会在任何情况下被调用"——不正确。 then() 在 Promise 内部发生错误的情况下永远不会被调用。
    • jQuery.Deferred() 可以接收多个值,并将其正确传递给第一个 .then() 的有趣方面。虽然有点奇怪......因为任何后续 .then() 都做不到所以。 (通过return 选择的接口只能返回一个值。)Javascript 的原生Promise 不这样做。 (老实说,这更一致。)
    【解决方案7】:

    只使用.then()

    这些是.done()的缺点

    • 不能链接
    • 阻止resolve()调用(所有.done()处理程序将同步执行)
    • resolve() 可能会从注册的 .done() 处理程序(!)中获得异常
    • .done() 中的异常会导致延迟:
      • 更多.done() 处理程序将被静默跳过

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

    证明

    以下代码 sn-p 显示:

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

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

    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) 为什么索引 2、4 和 6 处的 done 在第二个 then 之前执行?
    • 我的错,你绝对值得一票。至于您对异常的评论,通常这就是异常的工作方式:一旦引发,它之后的代码将不会被执行。再加上 jQuery 文档声明它只会在 deferred 被解决时才被执行。
    • @CodingYoshi 这里的情况有所不同:我只是在谈论已解决的承诺/延期。我不是在抱怨没有调用成功处理程序的其余部分,这是正常的。但是我看不出为什么不调用一个完全不同的成功承诺的成功处理程序。所有.then() 都将被调用,无论是否引发异常(在那些处理程序中)。但是添加/剩余 .done() 中断。
    • @CodingYoshi 我大大改进了我的答案,如果我可以说的话。代码和文本。
    【解决方案8】:

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

    【讨论】:

      【解决方案9】:

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

      考虑以下代码:

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

      这将输出:

      then
      now
      

      现在,在同一个 sn-p 中将 done() 替换为 then()

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

      现在的输出是:

      now
      then
      

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

      这不同于之前的 jQuery 版本,其中两个回调被同步调用,如 upgrade guide 中所述:

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

      【讨论】:

      • 谢谢。这个答案解释了我看到的行为。我正在使用then()。我的测试失败了,因为在测试结束后回调被称为异步。使用done()同步调用回调,满足测试预期,测试通过。
      【解决方案10】:

      除了上面的答案:

      .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 之后

      【讨论】:

        【解决方案11】:

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

        实际上,如果您不打算在承诺中附加更多步骤,则应使用.done()。更多详情见why promises need to be done

        【讨论】:

        • 小心!这个答案对于几个 Promise 实现是正确的,但不是 jQuery,其中 .done() 没有终止角色。文档说,“由于 deferred.done() 返回延迟对象,延迟对象的其他方法可以链接到这个对象,包括额外的 .done() 方法”。 .fail() 没有被提及,但是,是的,它也可以被链接起来。
        • 我的错,没有检查jQuery
        • @glebbahmutov - 也许您应该删除此答案,以免其他人感到困惑?只是一个友好的建议:)
        • 请不要删除答案,这也可以帮助人们消除误解。
        • 与其删除(错误的)答案,不如用错误的原因更新它会很有趣。它可以避免投票;)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-18
        • 2015-03-27
        • 2023-03-21
        相关资源
        最近更新 更多