jQuery $.Deferred (jQuery 1.x/2.x) 固有的问题
Posted
技术标签:
【中文标题】jQuery $.Deferred (jQuery 1.x/2.x) 固有的问题【英文标题】:Problems inherent to jQuery $.Deferred (jQuery 1.x/2.x) 【发布时间】:2014-07-07 19:38:49 【问题描述】:@Domenic 有一篇关于 jQuery 延迟对象的失败的非常详尽的文章:You're missing the Point of Promises。在其中,Domenic 强调了 jQuery 承诺与其他承诺相比的一些失败,包括Q、when.js、RSVP.js 和 ES6 承诺。
我从 Domenic 的文章中走出来,感觉 jQuery 承诺在概念上存在固有的缺陷。我正在尝试为这个概念举例说明。
我认为 jQuery 实现存在两个问题:
1。 .then
方法不可链接
换句话说
promise.then(a).then(b)
当满足promise
时,jQuery 将调用a
然后b
。
由于 .then
在其他 Promise 库中返回一个新的 Promise,它们的等价物将是:
promise.then(a)
promise.then(b)
2。异常处理在 jQuery 中冒泡。
另一个问题似乎是异常处理,即:
try
promise.then(a)
catch (e)
Q 中的等价物是:
try
promise.then(a).done()
catch (e)
// .done() re-throws any exceptions from a
在 jQuery 中,当 a
无法捕获块时,异常会抛出并冒泡。在其他承诺中,a
中的任何异常都将传递给.done
或.catch
或其他异步捕获。如果没有任何 Promise API 调用捕获到异常,它就会消失(因此 Q 的最佳实践是使用 .done
释放任何未处理的异常)。
上述问题是否涵盖了与 jQuery 实现 promise 的问题,还是我误解或遗漏了问题?
编辑这个问题与jQuery jQuery 3.0 alpha 开始,jQuery 是 Promises/A+ 兼容的。
【问题讨论】:
【参考方案1】:更新: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 承诺。
【讨论】:
显然 Bluebird 的 @Esailija 指出有一个黑暗的巫术技巧 - 从 jQuery 返回一个已履行的承诺(!)。然后错误处理程序会将承诺标记为已处理。 哇,我什至不知道那个,虽然我可以在源代码中确认它:-) 你可以把那个部分分成exceptions in callbacks
和handling errors
,因为它实际上是两个错误。
这不是一个黑暗的巫术技巧,无需查看源代码即可发现它。自 jQuery 1.8 发布以来,文档为 "these filter functions can return a new value to be passed along to the promise's .done() or .fail() callbacks, or they can return another observable object (Deferred, Promise, etc) which will pass its resolved / rejected status and values to the promise's callbacks"
。因此,这不是一个“大问题”,如果你能接受它,实际上根本就不是问题。
感谢这篇文章。在您编写console.log("This should have run");
和console.log("But this does instead");
的第一个问题示例中,这可能会让读者感到困惑,因为当然这些console.logs 都不是由于throw
语句而实际发生的。还是我误会了什么?
@BenjaminGruenbaum 很棒的帖子,谢谢。需要指出的一件事是发布 Zalgo 的小提琴正在使用最新版本的 jQuery,这似乎使 Zalgo 得到了控制。您可能需要更新小提琴以使用较旧的 2.x 版本的 jQuery,以便小提琴回到损坏状态。以上是关于jQuery $.Deferred (jQuery 1.x/2.x) 固有的问题的主要内容,如果未能解决你的问题,请参考以下文章