更新:jQuery 3.0 已经修复了下面列出的问题。它真正符合 Promises/A+ 标准。
是的,jQuery Promise 存在严重且固有的问题。
也就是说,自从写这篇文章以来,jQuery 做出了巨大的努力来引起更多的 Promises/Aplus 投诉,他们现在有一个链接的 .then 方法。
因此,即使在 jQueryreturnsPromise().then(a).then(b)
中用于 promise 返回函数a
,b
也会按预期工作,在继续前进之前解开返回值。如这个小提琴所示:
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 中的拒绝功能天生就被破坏并且非常难以使用,与 synchronous 完全不同try/catch
。
你能猜到这里有什么日志吗?(小提琴)
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 承诺不同,它们不会让您处理任何抛出的错误。拒绝安全呢?(小提琴)
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");
使用 Bluebird 和 Q 等 Promises/A+ 库的流程是什么,以及您对有用性的期望。这是巨大的,投掷安全是承诺的一大卖点。这是Bluebird 在这种情况下的正确行为。
执行顺序
如果底层的 promise 已经解析,jQuery 将立即执行传递的函数,而不是推迟它,因此代码的行为会有所不同,具体取决于我们附加处理程序拒绝的 promise 是否已经解析。这有效地释放了 Zalgo,并可能导致一些最痛苦的错误。这会产生一些最难调试的错误。
如果我们看下面的代码:(小提琴)
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)不需要.done
像 Q 那样在链的末端,因为它们自己找出未处理的拒绝,它们也比 jQuery Promise 或 Q Promise 快得多。