更新:jQuery 3.0 已经修复了下面列出的问题。它真正符合 Promises/A+ 标准。
是的,jQuery 承诺存在严重且固有的问题。
也就是说,自从写这篇文章以来,jQuery 做出了巨大的努力来引起更多的 Promises/Aplus 投诉,他们现在有一个链接的 .then 方法。
所以即使在 jQuery returnsPromise().then(a).then(b) for promise 返回函数 a 和 b 将按预期工作,在继续前进之前解开返回值。如fiddle所示:
function timeout(){
var d = $.Deferred();
setTimeout(function(){ d.resolve(); },1000);
return d.promise();
}
timeout().then(function(){
document.body.innerHTML = "First";
return timeout();
}).then(function(){
document.body.innerHTML += "<br />Second";
return timeout();
}).then(function(){
document.body.innerHTML += "<br />Third";
return timeout();
});
然而,jQuery 的两个巨大问题是错误处理和意外的执行顺序。
错误处理
没有办法将被拒绝的 jQuery 承诺标记为“已处理”,即使你解决了它,这与 catch 不同。这使得 jQuery 中的拒绝功能天生就被破坏并且很难使用,与同步 try/catch 完全不同。
你能猜出这里有什么日志吗? (fiddle)
timeout().then(function(){
throw new Error("Boo");
}).then(function(){
console.log("Hello World");
},function(){
console.log("In Error Handler");
}).then(function(){
console.log("This should have run");
}).fail(function(){
console.log("But this does instead");
});
如果您猜对了"uncaught Error: boo",那么您是对的。 jQuery 承诺是不安全的。与 Promises/Aplus 承诺不同,它们不会让您处理任何抛出的错误。拒绝安全呢? (fiddle)
timeout().then(function(){
var d = $.Deferred(); d.reject();
return d;
}).then(function(){
console.log("Hello World");
},function(){
console.log("In Error Handler");
}).then(function(){
console.log("This should have run");
}).fail(function(){
console.log("But this does instead");
});
以下日志"In Error Handler" "But this does instead" - 根本无法处理 jQuery 承诺拒绝。这与您期望的流程不同:
try{
throw new Error("Hello World");
} catch(e){
console.log("In Error handler");
}
console.log("This should have run");
使用 Promises/A+ 库(如 Bluebird 和 Q)获得的流程是什么,以及您对有用性的期望。这是巨大的,并且 throw 安全性是 promise 的一大卖点。这里是Bluebird acting correctly in this case。
执行顺序
jQuery 将立即 执行传递的函数,而不是在底层promise 已经解析的情况下延迟它,因此代码的行为会有所不同,具体取决于我们附加处理程序拒绝的promise 是否已经解析。这实际上是releasing Zalgo,可能会导致一些最痛苦的错误。这会产生一些最难调试的错误。
如果我们看下面的代码:(fiddle)
function timeout(){
var d = $.Deferred();
setTimeout(function(){ d.resolve(); },1000);
return d.promise();
}
console.log("This");
var p = timeout();
p.then(function(){
console.log("expected from an async api.");
});
console.log("is");
setTimeout(function(){
console.log("He");
p.then(function(){
console.log("̟̺̜̙͉Z̤̲̙̙͎̥̝A͎̣͔̙͘L̥̻̗̳̻̳̳͢G͉̖̯͓̞̩̦O̹̹̺!̙͈͎̞̬ *");
});
console.log("Comes");
},2000);
我们可以观察到哦如此危险的行为,setTimeout 等待原始超时结束,因此 jQuery 切换其执行顺序,因为......谁喜欢不会导致堆栈溢出的确定性 API?这就是为什么 Promises/A+ 规范要求承诺总是推迟到事件循环的下一次执行。
旁注
值得一提的是,像 Bluebird 这样更新和更强大的 Promise 库(以及实验性的 When)不需要像 Q 那样在链的末端使用 .done,因为它们自己找出未处理的拒绝,它们也比 jQuery 快得多承诺或 Q 承诺。