【问题标题】:Problems inherent to jQuery $.Deferred (jQuery 1.x/2.x)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+ 兼容的。

【问题讨论】:

    标签: jquery promise jquery-deferred q es6-promise


    【解决方案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,以便小提琴回到损坏状态。
    猜你喜欢
    • 2014-06-02
    • 2017-03-25
    • 1970-01-01
    • 2014-11-09
    • 1970-01-01
    • 1970-01-01
    • 2011-03-28
    • 1970-01-01
    相关资源
    最近更新 更多