异常被承诺链吞噬

Posted

技术标签:

【中文标题】异常被承诺链吞噬【英文标题】:Exception gets swallowed in promise chain 【发布时间】:2016-04-23 01:05:25 【问题描述】:

我注意到在 Parse for React Native 中的一系列承诺中抛出异常时发生了一些非常奇怪的事情。承诺链永远不会解析,永远不会拒绝,也永远不会抛出异常。它只是默默地消失了。

这里是重现问题的示例代码:

// Replacing this with Promise.resolve() prints the error.
// Removing this stage prints the error.
Parse.Promise.as()
  // Removing this stage causes a red screen error.
  .then(function() 
    // Replacing this with Parse.Promise.as() causes a red screen error.
    return Promise.resolve();
  )
  .then(function () 
    throw new Error("There was a failure");
  )
  .then(function ()  console.log("Success"), function (err)  console.log(err) );

正如您从 cmets 中看到的,它似乎只发生在这个特定的事件序列中。移除一个阶段,或者将 Parse 承诺换成原生 JS 承诺,会导致事情再次发生。 (在我的实际代码中,“Promise.resolve()”阶段实际上是对返回 Promise 的原生 ios 方法的调用。)

我知道 Parse Promise 的行为完全不像 A+ 兼容的 Promise(参见https://***.com/a/31223217/2397068)。事实上,在这段代码之前调用Parse.Promise.enableAPlusCompliant() 会导致异常被捕获并打印出来。但我认为 Parse Promise 和原生 JS Promise 可以安全地一起使用。

为什么这个异常会无声无息地消失?

谢谢。

【问题讨论】:

您可能已经回答了自己的问题。 @Bergi 的 accepted answer 表示 Parse 承诺不符合 A+ 默认情况下,并且第一个代码审查点说,在使它们符合标准之后“然后回调中的异常被捕获并导致结果被拒绝承诺,而不是全局错误”。看来您可以同时使用 Parse 和 JS 承诺提供您首先使 Parse 承诺兼容。 Parse.Promise.as(true).then(function() return Parse.Promise.error("here is an error"); ).then(function(done) console.log('done', done); , function(err) console.log('err', err); ); 也可以捕获错误.. @zangw 感谢 Parse 原生替代方案。我试图解释为什么 Promise.resolve 可以解决以下很多问题:-) @Traktor53 我知道,当它们符合 A+ 标准时,“异常......被捕获,而不是全局错误”,但我在这里报告的问题涉及异常完全消失! IE。没有全局错误。 @zangw 感谢您提供的有用提示!不幸的是,在我的实际代码中,异常是在第三方代码中引发的,我不能将其更改为被拒绝的承诺(不将其包装在 catch 块中,或嵌套另一个承诺,这对我来说违背了链接承诺的目的首先!)。 【参考方案1】:

为什么这个异常消失了?

默认情况下,Parse 不会捕获异常,而 Promise 会吞噬它们。

Parse 不是 100% 兼容 Promises/A+,但它确实尝试同化从 then 回调返回的 thenable。它既不捕获异常也不异步执行自己的回调。

不用then 也可以复制你正在做的事情

var p1 = new Parse.Promise();
var p2 = new Parse.Promise();

// p2 should eventually settle and call these:
p2._resolvedCallbacks = [function (res)  console.log("Success") ];
p2._rejectedCallbacks = [function (err)  console.log(err) ];

function handler() 
    throw new Error("There was a failure");

// this is what the second `then` call sets up (much simplified):
p1._resolvedCallbacks = [function(result) 
    p2.resolve(handler(result)); // throws - oops
];

// the native promise:
var p = Promise.resolve();
// what happens when a callback result is assimilated:
if (isThenable(p))
    p.then(function(result) 
        p1.resolve(result);
    );

问题在于p1.resolve 是同步的,并立即在p1 上执行回调——这反过来又会抛出。通过在p2.resolve 被调用之前抛出,p2 将永远处于等待状态。异常冒出并成为p1.resolve() 的完成 - 现在它会引发对本机then 方法的回调。原生 Promise 实现捕获异常并拒绝 then 返回的 Promise,但它在任何地方都会被忽略。

默默地?

如果您的“本机”promise 实现支持未处理的拒绝警告,您应该能够在被拒绝的 promise 中看到异常。

【讨论】:

感谢您的详细回复。您如何建议将来避免此问题?使用 A+ 兼容的承诺?最后添加一个 .catch 块? (参见pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html 中的“Rookie 错误 #3:忘记添加 .catch()”)另外,我是否需要像 bluebird 这样的第三方承诺库来获得“未处理的拒绝警告”? 我建议使用enableAPlusCompliant(),是的。如果您的承诺没有捕捉到异常,.catch 调用将无济于事。我认为您不需要第三方承诺库,afaik FF 和 Chrome 原生承诺都支持未处理的拒绝跟踪(也许您需要在开发工具中启用它)。当然,使用 Bluebird 绝不是错误 :-) 谢谢。我已经启用了 A+ 合规性,到目前为止一切都很好,尽管我发现了至少一个由此引入的相对高调的错误 (github.com/ParsePlatform/ParseReact/issues/161),这让我想知道还有多少潜在的错误。【参考方案2】:

除了您引用的答案中提供的技术原因外,供您考虑:

合规承诺

符合 ES6/A+ 的 Promise 实例共享:

    当一个 Promise 被结算时,它的结算状态和值是不可变的。 承诺无法通过承诺来实现。 绝不会使用 Promise(或其他 thenable)对象作为参数调用当时注册的 'fulfill' 侦听器。 then 注册的侦听器在导致它们执行的代码运行完成后,在它们自己的线程中异步执行。

    无论监听器是否注册回调,当一个承诺被解决(“履行”或“拒绝”),

    监听器的返回值用于履行 解决then注册返回的承诺 一个监听器抛出的值(使用throw)用于拒绝then注册返回的promise,并且

    使用 Promise 实例解析的 Promise 与作为参数提供的 Promise 的最终稳定状态和值同步。 (在 ES6 标准中,这被描述为“锁定”)。

    一个用thenable对象解决的promise 不是 Promise实例将根据需要跳过同步:如果首先用一个thenable解决,它用一个thenable“满足”自己,Promise promise将重新- 与提供的最新“thenable”同步。 A+ Promise 不会跳到新的 thenable 进行同步,因为它们从不使用 thenable 对象调用“已实现”的侦听器。

不合规的承诺

不符合承诺的对象的潜在特征包括

允许更改承诺的已解决状态, 使用 promise 参数调用当时“已实现”的侦听器, 从解析或拒绝承诺的代码同步调用 then 侦听器,以获取“已完成”或“已拒绝”状态, 在 then 调用中注册的侦听器引发异常后,不拒绝 then 返回的承诺。

互操作性

Promise.resolve 可用于以静态值解决其返回的承诺,可能主要用于测试。然而,它的主要目的是隔离不合规承诺的副作用。 Promise.resolve( thenable) 返回的 Promise 将表现出上述 1-7 的所有行为,并且没有任何不合规的行为。

恕我直言,我建议仅在环境中使用非 A+ 承诺对象,并根据创建它们的库的文档。不兼容的 thenable 可用于直接解析 A+ 承诺(这是 Promise.resolve 所做的),但为了行为的完全可预测性,它应该在任何其他用途之前使用 Promise.resolve( thenable) 包装在 Promise 对象中。

注意,我尝试测试 Parse 的 A+ 合规性承诺,但它似乎没有提供构造函数。这使得“几乎”或“完全”符合 A+ 标准的说法难以量化。感谢 zangw 指出了从侦听器返回被拒绝的承诺作为抛出异常的替代方法的可能性。

【讨论】:

嗨@Traktor53,我很好奇你对 Parse 承诺没有构造函数的意思。您只需致电new Parse.Promise() 即可获得。这是一个构造函数,不是吗? github.com/ParsePlatform/Parse-SDK-JS/blob/master/src/… 你是绝对正确的 - 奇怪的是,website API documention 中没有提到构造函数的用法,但 developers' guide 中涵盖了使用 Parse.Promise 作为构造函数。

以上是关于异常被承诺链吞噬的主要内容,如果未能解决你的问题,请参考以下文章

处理承诺异常的问题

Node.js 承诺和异步异常

如何跳过导致异常的链式承诺?

Try/catch 块和未处理的承诺异常

如何来解决编程异常链呢

如何在使用同构获取的异常处理承诺后解析 json