【问题标题】:Recovering from rejected promises in JS从 JS 中被拒绝的承诺中恢复
【发布时间】:2015-11-04 21:52:28
【问题描述】:

我正在使用本机承诺(大部分)并尝试从错误中恢复并继续执行承诺链。

实际上,我正在这样做:

  • REST 查询以查看 ID 是否存在。 请注意,这会返回延迟的 jquery。
  • .then(成功表示ID存在,失败则停止) (失败表示ID不存在,继续创建ID)
  • .then(创建ID记录并发送到服务器)

我从被拒绝的函数中返回了一个 Promise.resolve(),这应该会导致下一个 .then 的成功部分执行。它不是。我在 Chrome 和 Safari 上试过这个。

注意第一个promise实际上是一个延迟查询,但是根据这个页面(http://api.jquery.com/deferred.then/),deferred.then()返回一个promise对象。所以添加一个额外的 .then 应该隐藏到原生的 Promise 中。

为了更清楚 - 这是伪代码:

promise = $.ajax(url);
promise = promise.then();  // convert to promise 
promise.then(function() { cleanup(); return Promise.reject(); },
    function(err) { return Promise.resolve(); });
.then(function() { createIdentityDetails(); });
.then(function() { sendIdentityDetails(); });

请注意,我想在 ajax 返回成功时失败,并且我想 ajax 调用失败时继续处理。

发生的情况是所有后续 .then 部分的 FAIL 函数都会执行。也就是说,我的 return Promise.resolve() 不起作用 - 这(我认为)违反了规范。

对于如何处理长承诺链中的错误并从错误中恢复的任何反馈,我将不胜感激。

非常感谢您提供的任何建议。

附言创建和收集完整的身份信息非常耗时,所以如果 ID 存在,我不想这样做。因此,我想先检查并快速失败。

p.p.s 我真的很喜欢 Promise 解开这些深度嵌套的异步回调链的方式。

【问题讨论】:

  • cleanup()createIdentityDetails()sendIdentityDetails() 是同步的还是异步的?
  • 清理是同步的,其他的是异步的。

标签: javascript jquery deferred


【解决方案1】:

假设createIdentityDetails()sendIdentityDetails() 是返回承诺的异步函数...

如果我们在问题中看到的是整个 Promise 链,那么处理错误情况很简单。没有必要将成功转化为失败或失败转化为成功,或者从一种类型的承诺转换为另一种类型的承诺。

$.ajax(url).then(function() {
    cleanup();
}, function(err) {
    createIdentityDetails()
    .then(sendIdentityDetails);
});

无论createIdentityDetails() jQuery 或非 jQuery 返回的承诺类型如何,这都将起作用。

但是,如果还有更多内容,例如需要将结果通知调用方函数,那么您需要做更多事情,这取决于您希望如何报告可能的结果。

将“ID 已存在”报告为失败,将“新 ID 创建”报告为成功

这就是问题所暗示的

function foo() {
    return $.ajax(url).then(function() {
        cleanup();
        return $.Deferred().reject('failure: ID already exists');
    }, function(err) {
        return createIdentityDetails()
        .then(sendIdentityDetails)
        .then(function() {
            return $.when('success: new ID created');
        });
    });
}

将这两种结果都报告为成功

这似乎更明智,因为处理的错误将报告为成功。只有未预料到的、未处理的错误才会这样报告。

function foo() {
    return $.ajax(url).then(function() {
        cleanup();
        return 'success: ID already exists';
    }, function(err) {
        return createIdentityDetails()
        .then(sendIdentityDetails)
        .then(function() {
            return $.when('success: new ID created');
        });
    });
}

无论采用哪种报告策略,createIdentityDetails() 返回的承诺类型都非常重要。作为链中的第一个 Promise,它决定了链中的两个 .thens 的行为。

  • 如果 createIdentityDetails() 返回一个原生 ES6 承诺,那么不用担心,大多数承诺,甚至 jQuery,都会被同化。
  • 如果createIdentityDetails() 返回一个 jQuery 承诺,那么只有 jQuery 承诺将被同化。因此,sendIdentityDetails() 还必须返回一个 jQuery 承诺(或必须使用 $.Deferred(...) 重铸为 jQuery 的 ES6 承诺),最终成功转换器也必须返回(如上代码)。

你可以通过here这两种方式看到混合 jQuery 和 ES6 Promise 的效果。第一个警报是由第二个代码块生成的,不是预期的。第二个警报由第一个块生成,并正确给出结果 98 + 1 + 1 = 100。

【讨论】:

  • 感谢您的完整回复-我认为您的 jsfiddle 显示了两个几乎兼容的 Promise/async 系统的危险。似乎很容易射中自己的脚(就像我一样)。我担心 jQuery deferreds - 其他人说他们将来会改变以与 Promises/A+ 更兼容(参见 Bergi 的文章参考),这意味着如果你使用它们太多,则需要重新设计。
【解决方案2】:
promise = promise.then();  // convert to promise

嗯? $.ajax 返回的 Promise 已经是 Promise。

promise.then(function() { cleanup(); return Promise.reject(); },
 function(err) { return Promise.resolve(); });

问题在于 jQuery 与 Promises/A+ 不兼容,并且无法从其他实现中采用 promises/thenable 而不是它自己的。您必须use $.Deferred here 才能完成这项工作,例如

promise.then(function() { cleanup(); return $.Deferred().reject(); },
             function() { return $.when(); }); // or return $.Deferred().resolve();

也就是说,我的return Promise.resolve() 不起作用 - 这(我认为)违反了规范。

确实如此。但是,jQuery is known for this,他们直到 v3.0 才会修复它。

要使您想要使用的原生Promise 库正常工作,您需要避免使用jQuery 的then。这个can easily be done

var $promise = $.ajax(url);
var promise = Promise.resolve($promise);  // convert to proper promise 
promise.then(function() {
    cleanup();
    throw undefined;
}, function(err) {
    return undefined;
})
.then(createIdentityDetails)
.then(sendIdentityDetails);

【讨论】:

  • 感谢您的详细回复。我不知道那篇文章详细介绍了 $.Deferred 的问题-谢谢指出。我喜欢转换为原生承诺的选项 - 当/如果 jQuery 将它们的 Deferreds 与 Promises/A+ 对齐时,这可能意味着更少的重新设计。
【解决方案3】:

JQuery 承诺似乎不允许您将失败变为成功。但是,如果您使用原生 Promise,则可以。

例如:

Promise.resolve()
    .then(function() {console.log("First success"); return Promise.reject(); },
        function() { console.log("First fail"); return Promise.resolve(); })
    .then(function() {console.log("Second success"); return Promise.reject(); },
        function() { console.log("Second fail"); return Promise.resolve(); })
    .then(function() {console.log("Third success"); return Promise.reject(); },
        function() { console.log("Third fail"); return Promise.resolve(); })

在这里,我从第一个成功处理程序返回拒绝。在第二个失败处理程序中,我返回一个解析。这一切都按预期工作。输出是(Chrome):

First success
Second fail
Third success

事实证明,处理 jQuery 延迟和承诺的正确方法是强制转换它们:

var jsPromise = Promise.resolve($.ajax('/whatever.json'));

(来自http://www.html5rocks.com/en/tutorials/es6/promises/)。 这很好用,所以如果您将上面的初始行更改为:

Promise.resolve($.ajax("this will fail"))
...

你正确得到:

First fail
Second success
Third fail

底线...演员推迟到尽快承诺,然后一切似乎都正常。

【讨论】:

  • 不幸的是,这个答案的基本前提是不正确的 - JQuery 承诺 do 允许您将失败更改为成功(反之亦然)。但是,实现这一点所需的语法与 Promises/A+ 不同。
  • 谢谢 - 我看到您在下面概述了延迟方法。非常感谢。
【解决方案4】:

希望这会澄清一些事情,你有几个流浪;在 then 函数中你正在做你不需要做的事情

首先,我确定你不想要

promise = promise.then();

行,代码如下所示

promise = $.ajax(url);
promise.then(function() { 
    cleanup(); 
    throw 'success is an error'; // this is equivalent to return Promise.reject('success is an error');
}, function(err) { 
    return 'failure is good';  // returning here means you've nullified the rejection
}) // remove the ; you had on this line
.then(function() { createIdentityDetails(); }) // remove the ; on this line
.then(function() { sendIdentityDetails(); }) // remove the ; on this line
.catch(function(err) { }); // you want to catch the error thrown by success

【讨论】:

  • jQuery 承诺目前不符合 Promises/A+。如果正如它所显示的那样,promise 是一个 jqXHR,那么 - (a) 它没有 .catch() 方法并且整个 promise.then(...)...; 表达式将引发错误。 (b) 即使.catch() 已修复,jQuery 的承诺也不是抛出安全的,所以throw ... 保证不会被捕获。 (c) return 'failure is good' 不会取消拒绝。 jQuery 要求返回的 promise 被拒绝。可以在不将 jqXHR 强制为原生承诺的情况下解决问题,但不是这样。
  • promise=promise.then() 行实际上对最终结果没有影响。根据 jQuery - 它将 Deferred 转换为 Promise,但我认为不会。多余的“;”都是转录错误 - 道歉。
  • "返回'失败为好';"无法清理故障。漫游者 - 你提到问题可以在不强制的情况下解决......我会对你的想法感兴趣。最后我所做的不是混合延迟和原生承诺。换句话说,在 deferred 的失败路径中创建一个新的 Promise。
  • @Paul The "return 'failure is good';" does not work to clean up the failure. - 这可能是 jQueery Promise 的副作用 - 请参阅 jsfiddle.net/z7fzx2y3 - 你可以看到它有效.. 我猜你可以添加 fudge 代码
  • 我看到你回答了你自己的问题 - 很高兴知道 jquery“承诺”可以很容易地修复
猜你喜欢
  • 2015-10-26
  • 1970-01-01
  • 2023-01-16
  • 2018-05-09
  • 2017-03-28
  • 1970-01-01
  • 2016-07-08
  • 1970-01-01
  • 2015-10-28
相关资源
最近更新 更多