我一直在阅读有关 jQuery 延迟和承诺的信息,但我看不出使用 .then()
& 之间的区别.done()
用于成功的回调。我知道 Eric Hynds 提到 .done()
和 .success()
映射到相同的功能,但我猜 .then()
也是如此,因为所有回调都在成功操作完成时调用。
谁能告诉我正确的用法?
解决延迟后,将触发附加到 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()
也是如此。
处理返回结果的方式也有所不同(称为链接,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
})
done
对结果没有任何影响,而 then
会更改结果。其他人错过了很大的一点。
then
的行为在 1.8 中发生了变化
done
示例类似。在 pre-1.8 中将 then
更改为 pipe
以获得 1.8+ then
行为。
.done()
只有一个回调,它是成功回调
.then()
有成功和失败回调
.fail()
只有一个失败回调
所以你必须做什么取决于你……你关心它是成功还是失败?
then()
与 done()
大不相同。由于 then()
通常仅与成功回调一起调用,因此您的观点是一个细节,而不是要记住/知道的主要内容。 (不能说它在 jQuery 3.0 之前是怎样的。)
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
案例
实际上有一个非常关键的区别,因为 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 中返回裸值的函数不会“留下”像那些传递给的那样被拒绝。然后,他们解决了!)
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作为其结果。
jQuery.Deferred()
可以接收多个值,并将其正确传递给第一个 .then()
。虽然有点奇怪......因为任何后续 .then()
都不能这样做。 (通过 return
选择的接口只能返回一个值。)Javascript 的原生 Promise
不这样做。 (老实说,这更一致。)
仅使用 .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()
同步调用回调,满足测试预期,测试通过。
除了上面的答案:
.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
.done()
终止承诺链,确保没有其他东西可以附加进一步的步骤。这意味着 jQuery Promise 实现可以抛出任何未处理的异常,因为没有人可以使用 .fail()
处理它。
实际上,如果您不打算在承诺中附加更多步骤,则应使用 .done()
。有关详细信息,请参阅 why promises need to be done
.done()
没有终止角色。文档说,“由于 deferred.done() 返回延迟对象,延迟对象的其他方法可以链接到这个,包括额外的 .done() 方法”。 .fail()
没有被提及,但是,是的,它也可以被链接起来。
then
返回一个新的承诺是我错过的一个关键。我不明白为什么像$.get(....).done(function(data1) { return $.get(...) }).done(function(data2) { ... })
这样的链因data2
undefined 而失败;当我将done
更改为then
时,它起作用了,因为我真的想将 Promise 连接在一起,而不是在原始 Promise 上附加更多的处理程序。done
还是then
?为什么?.then()
)。