47

@Domenic 有一篇关于 jQuery 延迟对象的失败的非常详尽的文章:你错过了承诺点。在其中 Domenic 强调了 jQuery 承诺与其他承诺相比的一些失败,包括Q、when.js、RSVP.js 和 ES6 承诺。

我从 Domenic 的文章中走开,感觉 jQuery 承诺在概念上存在固有的缺陷。我试图为这个概念举例。

我认为 jQuery 实现有两个问题:

1..then方法不可链接

换句话说

promise.then(a).then(b)

jQuery 将ab满足时调用promise

由于.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
}

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

 

上面的问题是否涵盖了与 jQuery 实现承诺的问题,还是我误解或遗漏了问题?


编辑 这个问题与 jQuery < 3.0; 从jQuery 3.0 alpha开始, jQuery 是 Promises/A+ 兼容的。

4

1 回答 1

54

更新:jQuery 3.0 已经修复了下面列出的问题。它真正符合 Promises/A+ 标准。

是的,jQuery Promise 存在严重且固有的问题。

也就是说,自从写这篇文章以来,jQuery 做出了巨大的努力来引起更多的 Promises/Aplus 投诉,他们现在有一个链接的 .then 方法。

因此,即使在 jQueryreturnsPromise().then(a).then(b)中用于 promise 返回函数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 的两个问题是错误处理和意外的执行顺序。

错误处理

没有办法将被拒绝的 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 快得多。

于 2014-05-19T18:31:33.850 回答