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 返回函数 ab 将按预期工作,在继续前进之前解开返回值。如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 callbackshandling 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) 固有的问题的主要内容,如果未能解决你的问题,请参考以下文章

jQuery异步框架探究2:jQuery.Deferred方法

jQuery的Deferred对象

jquery中的 deferred之 deferred对象

jQuery源码学习:Deferred Object

jQuery: Deferred

jquery.min.js:'jQuery.Deferred 异常:$(...).dialog 不是函数'错误