ChatGPT解决这个技术问题 Extra ChatGPT

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

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

谁能告诉我正确的用法?

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

C
Community

解决延迟后,将触发附加到 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())。
L
Lu4

处理返回结果的方式也有所不同(称为链接,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 直截了当。如果有人想查看混合了 donethen 调用的链,我创建了一个 runnable example
上面的例子还强调了 'done' 作用于最初创建的原始 promise 对象,但 'then' 返回一个新的 promise。
这适用于 jQuery 1.8+。旧版本的行为与 done 示例类似。在 pre-1.8 中将 then 更改为 pipe 以获得 1.8+ then 行为。
w
whoan

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

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

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

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


您没有提到“then”会产生调用链。见 Lu4 的回答。
您的答案来自 2011 年......如今,它们的返回值使 then()done() 大不相同。由于 then() 通常仅与成功回调一起调用,因此您的观点是一个细节,而不是要记住/知道的主要内容。 (不能说它在 jQuery 3.0 之前是怎样的。)
N
Nipuna

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

如果没有提供 fail 回调,您的帖子没有说明 then 的行为 - 即根本没有捕获 fail 案例
失败案例引发了一个异常,该异常可以被程序的顶层捕获。您还可以在 JavaScript 控制台中看到异常。
C
Community

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


J
JasmineOT

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 不这样做。 (老实说,这更一致。)
R
Robert Siemer

仅使用 .then()

这些是 .done() 的缺点

不能上链

阻止 resolve() 调用(所有 .done() 处理程序将同步执行)

resolve() 可能会从注册的 .done() 处理程序(!)

.done() 中的异常会导致延迟执行一半:进一步的 .done() 处理程序将被静默跳过

进一步的 .done() 处理程序将被静默跳过

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

证明

以下代码片段显示:

所有 .done() 处理程序将在 resolve() 点同步调用,并在脚本跌至底部之前记录为 1、3、5、7

记录为 1、3、5、7

在脚本落入底部之前记录

.done() 中的异常会影响通过在 resolve() 周围的 catch 记录的 resolve() 调用者

通过在 resolve() 周围捕获

未记录进一步 .done() 分辨率 8 和 10 的异常违反承诺!

8 和 10 未记录!

.then() 在线程空闲后没有记录为 2、4、6、9、11 的这些问题(片段环境似乎没有 unhandledrejection)

线程空闲后记录为 2、4、6、9、11

(片段环境似乎没有 unhandledrejection)

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

console.log('脚本开始');让 deferred = $.Deferred(); // deferred.resolve('赎回。'); 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 '从 4-then() 抛出';}); deferred.done(() => console.log('5-done()')); deferred.then(() => console.log('6-then()')); deferred.done(() =>{console.log('7-done()-throw'); throw '从 7-done() 抛出';}); deferred.done(() => console.log('8-done()')); deferred.then(() => console.log('9-then()')); console.log('正在解决。');尝试 { deferred.resolve('Solution.'); } catch(e) { console.log(`在 resolve() 中从处理程序中捕获异常:`, e); deferred.done(() => console.log('10-done()')); deferred.then(() => console.log('11-then()')); console.log('脚本结束');

这将输出:

then
now

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

var d = $.Deferred(); d.then(() => console.log('then')); d.resolve(); console.log('现在');

现在的输出是:

now
then

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

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

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


谢谢你。这个答案解释了我看到的行为。我正在使用 then()。我的测试失败了,因为在测试结束后回调被称为异步。使用 done() 同步调用回调,满足测试预期,测试通过。
S
Stephan van Hoof

除了上面的答案:

.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


g
gleb bahmutov

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

实际上,如果您不打算在承诺中附加更多步骤,则应使用 .done()。有关详细信息,请参阅 why promises need to be done


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