@Domenic has a very thorough article on the failings of jQuery deferred objects: You're missing the Point of Promises. In it Domenic highlights a few failings of jQuery promises in comparison to others including Q, when.js, RSVP.js and ES6 promises.
@Domenic发表了一篇关于jQuery延迟对象失败的文章:你忽略了承诺的意义。在这篇文章中,Domenic强调了jQuery承诺与Q等其他承诺的一些不足之处。js,发请帖。js和ES6承诺。
I walk away from Domenic's article feeling that jQuery promises have an inherent failing, conceptually. I am trying to put examples to the concept.
我离开了Domenic的文章,感觉jQuery承诺在概念上有一个固有的缺陷。我想给这个概念举一些例子。
I gather there are two concerns with the jQuery implementation:
我猜想jQuery实现有两个问题:
1. The .then
method is not chainable
In other words
换句话说
promise.then(a).then(b)
jQuery will call a
then b
when the promise
is fulfilled.
当承诺实现时,jQuery将调用a然后b。
Since .then
returns a new promise in the other promise libraries, their equivalent would be:
然后在其他承诺库中返回一个新的承诺,它们的对等项是:
promise.then(a)
promise.then(b)
2. The exception handling is bubbled in jQuery.
The other issue would seem to be exception handling, namely:
另一个问题似乎是例外处理,即:
try {
promise.then(a)
} catch (e) {
}
The equivalent in Q would be:
Q中的等效项是:
try {
promise.then(a).done()
} catch (e) {
// .done() re-throws any exceptions from a
}
In jQuery the exception throws and bubbles when a
fails to the catch block. In the other promises any exception in a
would be carried through to the .done
or .catch
or other async catch. If none of the promise API calls catch the exception it disappears (hence the Q best-practice of e.g. using .done
to release any unhandled exceptions).
在jQuery中,当a失败捕获块时,异常会抛出和冒泡。在另一个承诺中,a中的任何异常都将传递到.done或.catch或其他异步捕获。如果所有的promise API调用都没有捕获异常,那么它就会消失(因此有Q最佳实践,例如使用.done来释放任何未处理的异常)。
Do the problems above cover the concerns with the jQuery implementation of promises, or have I misunderstood or missed issues?
上面的问题是涉及到jQuery承诺实现的问题,还是我误解或遗漏了问题?
Edit This question relates to jQuery < 3.0; as of jQuery 3.0 alpha jQuery is Promises/A+ compliant.
编辑此问题涉及jQuery < 3.0;在jQuery 3.0中,alpha jQuery是承诺/A+兼容的。
1 个解决方案
#1
52
Update: jQuery 3.0 has fixed the problems outlined below. It is truly Promises/A+ compliant.
更新:jQuery 3.0已经修复了下面列出的问题。它是真正承诺/A+兼容的。
Yes, jQuery promises have serious and inherent problems.
That said, since the article was written jQuery made significant efforts to be more Promises/Aplus complaint and they now have a .then method that chains.
这就是说,自从这篇文章被写下来之后,jQuery就做出了更大的努力来做出更多的承诺/Aplus抱怨,现在他们有了一个这样的方法。
So even in jQuery returnsPromise().then(a).then(b)
for promise returning functions a
and b
will work as expected, unwrapping the return value before continuing forward. As illustrated in this fiddle:
因此,即使在jQuery returnsPromise()中,然后(a)然后(b)承诺返回的函数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();
});
However, the two huge problems with jQuery are error handling and unexpected execution order.
Error handling
There is no way to mark a jQuery promise that rejected as "Handled", even if you resolve it, unlike catch. This makes rejections in jQuery inherently broken and very hard to use, nothing like synchronous try/catch
.
与catch不同的是,即使您解决了jQuery,也无法将拒绝的jQuery承诺标记为“已处理”。这使得jQuery中的拒绝本身就被破坏了,并且很难使用,没有同步try/catch。
Can you guess what logs here? (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");
});
If you guessed "uncaught Error: boo"
you were correct. jQuery promises are not throw safe. They will not let you handle any thrown errors unlike Promises/Aplus promises. What about reject safety? (fiddle)
如果你猜的是“未被发现的错误: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");
});
The following logs "In Error Handler" "But this does instead"
- there is no way to handle a jQuery promise rejection at all. This is unlike the flow you'd expect:
下面的日志是“In Error Handler”“但是这是错误处理程序”——根本没有办法处理jQuery的拒绝承诺。这与你预期的流程不同:
try{
throw new Error("Hello World");
} catch(e){
console.log("In Error handler");
}
console.log("This should have run");
Which is the flow you get with Promises/A+ libraries like Bluebird and Q, and what you'd expect for usefulness. This is huge and throw safety is a big selling point for promises. Here is Bluebird acting correctly in this case.
这就是像Bluebird和Q这样的承诺/A+库的流程,以及您期望的有用性。这是巨大的,而且安全是承诺的一个大卖点。这是蓝知更鸟在这种情况下的表现。
Execution order
jQuery will execute the passed function immediately rather than deferring it if the underlying promise already resolved, so code will behave differently depending on whether the promise we're attaching a handler to rejected already resolved. This is effectively releasing Zalgo and can cause some of the most painful bugs. This creates some of the hardest to debug bugs.
jQuery将立即执行传递的函数,而不是在底层的承诺已经解析时延迟它,因此代码的行为将根据我们附加的承诺是否已经解析而有所不同。这有效地释放了Zalgo,并可能导致一些最痛苦的bug。这就产生了一些最难调试的bug。
If we look at the following code: (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);
We can observe that oh so dangerous behavior, the setTimeout
waits for the original timeout to end, so jQuery switches its execution order because... who likes deterministic APIs that don't cause stack overflows? This is why the Promises/A+ specification requires that promises are always deferred to the next execution of the event loop.
我们可以观察到,oh如此危险的行为,setTimeout等待原始超时结束,因此jQuery切换了它的执行顺序,因为……谁喜欢不导致堆栈溢出的确定性api ?这就是为什么promise /A+规范要求承诺总是延迟到事件循环的下一个执行。
Side note
Worth mentioning that newer and stronger promise libraries like Bluebird (and experimentally When) do not require .done
at the end of the chain like Q does since they figure out unhandled rejections themselves, they're also much much faster than jQuery promises or Q promises.
值得一提的是,像Bluebird(以及在实验中)这样更新更强的承诺库不需要像Q那样在链的末端完成,因为它们自己找到了未处理的拒绝,它们也比jQuery承诺或Q承诺要快得多。
#1
52
Update: jQuery 3.0 has fixed the problems outlined below. It is truly Promises/A+ compliant.
更新:jQuery 3.0已经修复了下面列出的问题。它是真正承诺/A+兼容的。
Yes, jQuery promises have serious and inherent problems.
That said, since the article was written jQuery made significant efforts to be more Promises/Aplus complaint and they now have a .then method that chains.
这就是说,自从这篇文章被写下来之后,jQuery就做出了更大的努力来做出更多的承诺/Aplus抱怨,现在他们有了一个这样的方法。
So even in jQuery returnsPromise().then(a).then(b)
for promise returning functions a
and b
will work as expected, unwrapping the return value before continuing forward. As illustrated in this fiddle:
因此,即使在jQuery returnsPromise()中,然后(a)然后(b)承诺返回的函数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();
});
However, the two huge problems with jQuery are error handling and unexpected execution order.
Error handling
There is no way to mark a jQuery promise that rejected as "Handled", even if you resolve it, unlike catch. This makes rejections in jQuery inherently broken and very hard to use, nothing like synchronous try/catch
.
与catch不同的是,即使您解决了jQuery,也无法将拒绝的jQuery承诺标记为“已处理”。这使得jQuery中的拒绝本身就被破坏了,并且很难使用,没有同步try/catch。
Can you guess what logs here? (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");
});
If you guessed "uncaught Error: boo"
you were correct. jQuery promises are not throw safe. They will not let you handle any thrown errors unlike Promises/Aplus promises. What about reject safety? (fiddle)
如果你猜的是“未被发现的错误: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");
});
The following logs "In Error Handler" "But this does instead"
- there is no way to handle a jQuery promise rejection at all. This is unlike the flow you'd expect:
下面的日志是“In Error Handler”“但是这是错误处理程序”——根本没有办法处理jQuery的拒绝承诺。这与你预期的流程不同:
try{
throw new Error("Hello World");
} catch(e){
console.log("In Error handler");
}
console.log("This should have run");
Which is the flow you get with Promises/A+ libraries like Bluebird and Q, and what you'd expect for usefulness. This is huge and throw safety is a big selling point for promises. Here is Bluebird acting correctly in this case.
这就是像Bluebird和Q这样的承诺/A+库的流程,以及您期望的有用性。这是巨大的,而且安全是承诺的一个大卖点。这是蓝知更鸟在这种情况下的表现。
Execution order
jQuery will execute the passed function immediately rather than deferring it if the underlying promise already resolved, so code will behave differently depending on whether the promise we're attaching a handler to rejected already resolved. This is effectively releasing Zalgo and can cause some of the most painful bugs. This creates some of the hardest to debug bugs.
jQuery将立即执行传递的函数,而不是在底层的承诺已经解析时延迟它,因此代码的行为将根据我们附加的承诺是否已经解析而有所不同。这有效地释放了Zalgo,并可能导致一些最痛苦的bug。这就产生了一些最难调试的bug。
If we look at the following code: (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);
We can observe that oh so dangerous behavior, the setTimeout
waits for the original timeout to end, so jQuery switches its execution order because... who likes deterministic APIs that don't cause stack overflows? This is why the Promises/A+ specification requires that promises are always deferred to the next execution of the event loop.
我们可以观察到,oh如此危险的行为,setTimeout等待原始超时结束,因此jQuery切换了它的执行顺序,因为……谁喜欢不导致堆栈溢出的确定性api ?这就是为什么promise /A+规范要求承诺总是延迟到事件循环的下一个执行。
Side note
Worth mentioning that newer and stronger promise libraries like Bluebird (and experimentally When) do not require .done
at the end of the chain like Q does since they figure out unhandled rejections themselves, they're also much much faster than jQuery promises or Q promises.
值得一提的是,像Bluebird(以及在实验中)这样更新更强的承诺库不需要像Q那样在链的末端完成,因为它们自己找到了未处理的拒绝,它们也比jQuery承诺或Q承诺要快得多。