jQuery固有的问题$ .Deferred(jQuery 1.x / 2.x)

@Domenic在jQuery延期对象的缺陷方面有一篇非常透彻的文章: 你错过了 Promise的要点。 其中,Domenic突出了jQuery承诺的一些缺点,包括Q ,when.js,RSVP.js和ES6承诺。

我从Domenic的文章中离开,感觉jQuery的承诺在概念上有一个固有的失败。 我正在试图把这个概念的例子。

我收集了jQuery实现的两个问题:

那么这个方法是不可链接的

换一种说法

 promise.then(a).then(b) 

jQuery将在promise满足时调用a then b

因为如果在其他承诺库中返回新的承诺,它们的等价物将是:

 promise.then(a) promise.then(b) 

2.exception处理在jQuery中冒泡。

另一个问题似乎是exception处理,即:

 try { promise.then(a) } catch (e) { } 

Q中的等价物将是:

 try { promise.then(a).done() } catch (e) { // .done() re-throws any exceptions from a } 

在jQuery中,当catch块失败时抛出exception并且冒泡。 在其他承诺中, a任何exception都将传递给.done.catch或其他asynchronous捕获。 如果承诺的API调用都没有捕获到它会消失的exception(因此Q是使用.done释放任何未处理的exception的最佳做法)。

上述问题是否涵盖了jQuery实现promise的问题,还是我误解或错过了一些问题?


编辑这个问题涉及到jQuery <3.0; 作为jQuery 3.0 alpha jQuery是Promises / A +兼容。

更新:jQuery 3.0已经解决了下面列出的问题。 这是真正的承诺/ A +兼容。

是的,jQuery的承诺有严重和固有的问题。

也就是说,自从写了这篇文章之后,jQuery做出了很大的努力来做出更多的Promises / Aplus投诉,现在他们有了一个连锁的方法。

因此,即使在jQuery的returnsPromise().then(a).then(b)承诺返回函数ab将按预期工作,展开返回值继续前进。 正如这个小提琴所表明的那样:

 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的两个问题是error handling和意外的执行顺序。

error handling

没有办法标记一个jQuery的承诺,即使你解决它,拒绝作为“处理”,不像catch。 这使jQuery中的拒绝本质上是破碎的,很难使用,就像同步的try/catch

你能猜到这里有什么logging吗? ( 小提琴 )

 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的承诺是不安全的 。 他们不会让你处理任何抛出的错误不像承诺/ 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承诺拒绝。 这与您所期望的stream程不同:

 try{ throw new Error("Hello World"); } catch(e){ console.log("In Error handler"); } console.log("This should have run"); 

你用Bluebird和Q这样的Promises / A +库得到的stream程是什么,以及你期望的有用性。 这是巨大的 ,抛出安全是承诺的一大卖点。 蓝鸟在这种情况下正确行事 。

执行顺序

jQuery将立即执行传递的函数而不是延迟它,如果底层的承诺已经解决,所以代码的行为会有所不同,取决于我们附加处理程序拒绝的承诺是否已经解决。 这是有效释放Zalgo,并可能导致一些最痛苦的错误。 这创build了一些最难debugging的错误。

如果我们看下面的代码:( 小提琴 )

 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这样的更新,更强大的承诺库(和实验性的时候)并不需要象Q那样需要完成,因为他们自己找出未处理的拒绝,它们也比jQuery承诺或Q promise 。