【问题标题】:How to reject (and properly use) Promises?如何拒绝(并正确使用)Promise?
【发布时间】:2015-04-29 22:42:49
【问题描述】:

短篇小说

  • 谈到 Promises/A+,拒绝承诺的正确方法是什么 - 抛出错误?但是如果我错过了catch - 我的整个应用程序都会崩溃!

  • 如何使用promisify,它有什么好处(也许你需要阅读更长的版本)?

  • .then(success, fail) 真的是anti-pattern,我应该一直使用.then(success).catch(error)吗?

加长版,描述为现实生活中的问题(希望有人阅读):

我有一个使用Bluebird(A+ Promise 实现库)的库,用于从数据库中获取数据(谈论Sequelize)。每个查询都会返回一个结果,但有时它是空的(试图选择一些东西,但没有任何结果)。承诺进入result 函数,因为没有错误的理由(没有结果不是错误)。示例:

Entity.find(1).then(function(result) {
  // always ending here, despite result is {}
}).catch(function(error) {
  // never ends here
});

我想包装它并检查结果是否为空(无法在我的代码中到处检查)。我这样做了:

function findFirst() {
  return Entity.find(1).then(function(result) {
    if (result) { // add proper checks if needed
      return result; // will invoke the success function
    } else {
      // I WANT TO INVOKE THE ERROR, HOW?! :)
    }
  }).catch(function(error) {
    // never ends here
  });
}

findFirst().then(function(result) {
  // I HAVE a result
}).catch(function(error) {
  // ERROR function - there's either sql error OR there is no result!
});

如果你还在我身边 - 我希望你能明白发生了什么。我想以某种方式启动错误功能(“错误功能”在哪里)。问题是 - 我不知道怎么做。我试过这些东西:

this.reject('reason'); // doesn't work, this is not a promise, Sequelize related
return new Error('reason'); // calls success function, with error argument
return false; // calls success function with false argument
throw new Error('reason'); // works, but if .catch is missing => BLOW!

正如您在我的 cmets 中看到的(并且根据规范),抛出错误效果很好。但是,有一个很大的 但是 - 如果我错过了 .catch 声明,我的整个应用程序就会崩溃。

为什么我不想要这个?假设我想在我的数据库中增加一个计数器。我不关心结果——我只是发出 HTTP 请求。所以我可以调用incrementInDB(),它可以返回结果(即使是出于测试原因),所以如果它失败了会有throw new Error。但由于我不关心响应,有时我不会添加 .catch 语句,对吗?但是现在 - 如果我不这样做(故意或由于错误) - 我最终会导致您的节点应用程序关闭。

我觉得这不是很好。有没有更好的方法来解决它,或者我只需要处理它?

我的一个朋友帮助了我,我使用了一个新的承诺来解决问题,如下所示:

function findFirst() {
  var deferred = new Promise.pending(); // doesnt' matter if it's Bluebird or Q, just defer
  Entity.find(1).then(function(result) {
    if (result) { // add proper checks if needed
      deferred.resolve(result);
    } else {
      deferred.reject('no result');
    }
  }).catch(function(error) {
    deferred.reject('mysql error');
  );

  return deferred.promise; // return a promise, no matter of framework
}

像魅力一样工作! 但是我进入了这个:Promise Anti Patterns - 由 Bluebird 的创建者 Petka Antonov 撰写的 wiki 文章(A+ 实现)。明确表示这是错误

所以我的第二个问题是 - 是这样吗?如果是 - 为什么?最好的方法是什么?

非常感谢您阅读本文,我希望有人能花时间为我解答 :) 我应该补充一点,我不想过多依赖框架,所以 SequelizeBluebird 只是事情我最终与之合作。我的问题是 Promises 作为一个全局的,而不是这个特定的框架。

【问题讨论】:

  • But, there's a big but - if I miss the .catch statement, my whole app blows up. 这不就是例外吗?您应该始终处理异常,即使您不关心失败,您也应该在代码中通过添加 .catch 和您不关心的注释来明确这一点。想象一下,稍后您将再次查看您的代码并发现有一个未处理的异常,但您不记得为什么它未处理,那么您将花费大量时间来确定原因。
  • 这是关于生产力,与多个开发人员合作,而不是最后 - 可靠性。任何开发人员只写incrementInDB(); 比浏览代码更容易,读到这是一个会引发错误的承诺,所以你必须抓住它。正如我所说 - 我不想要例外 - 我只是想不出另一种方法来做到这一点而不炸毁我的整个应用程序:)
  • 我认为 t.niese 的评论应该是一个答案。是的,任何开发人员编写一行代码可能比编写正确代码更容易,但这不是借口。我经常看到来自没有经验的开发人员的代码,其中一些 API 抛出的异常被默默地吞下并转换为返回 null 等。一些开发人员似乎对异常有某种基本的恐惧。正确使用 catch() 和 done() 我真的没有看到任何问题。此外,如果需要,您总是可以编写自己的未捕获异常处理程序?
  • 感谢您的回复。我只是觉得处于两个极端很奇怪——要么使用异常,要么总是使用它们。在编程中,单个请求中有更多的情况——它可以返回多种类型的响应(甚至是错误类型)。我的意思是 - 我不能总是祈祷团队的开发人员都没有忘记使用.catch,因此我们的产品生产中不会完全崩溃..可靠性最重要
  • 我不明白如何拒绝承诺或使用传达所有内容的值解决问题而不是抛出异常是一种反模式。 Antonov 似乎只是在争论应该使用.catch() 而不是.then() 中的fail 处理程序。这对我来说似乎纯粹是一个风格问题,他只是在表达他的意见。除了将 try/catch 用于同步代码的粗略类比之外,他没有为该观点提供任何令人信服的理由。但是,在同步代码中也有数百种其他方式返回错误。

标签: javascript node.js promise bluebird


【解决方案1】:

谈到 Promises/A+,拒绝承诺的正确方法是什么——抛出错误?但如果我错过了抓住机会 - 我的整个应用程序都会崩溃!

如果您使用返回码来表示错误而不是异常,那么错过检查将导致细微的错误和不稳定的行为。忘记处理错误是一个错误,但让您的应用在这种不幸的情况下崩溃是more often better,而不是让它继续处于损坏状态。

调试被遗忘的错误代码检查仅在应用程序在某些与错误源无关的地方表现得很奇怪时很容易比调试被遗忘的捕获要昂贵许多数量级,因为您有错误消息,错误来源和堆栈跟踪。因此,如果您希望在典型的应用程序开发场景中提高生产力,那么异常会以相当大的优势获胜。

.then(成功,失败)

这通常表明您正在使用 Promise 作为美化回调,其中回调只是单独传递。这显然是一种反模式,因为您不会从以这种方式使用 Promise 中获得任何好处。如果不是这种情况,即使这样,与使用 .catch() 相比,您也只是让代码的可读性稍差,类似于 method(true, true, false) 的可读性远低于 method({option1: true, option2: true, option3: false})

如何使用 promisify,它有什么好处(也许你需要阅读更长的版本)?

大部分模块只暴露了一个回调接口,promisify 自动把一个回调接口变成了一个promise 接口。如果您手动编写此类代码(使用 deferred 或 new Promise),您就是在浪费时间。

所以我的第二个问题是 - 是这样吗?如果是 - 为什么?最好的方法是什么?

最好的方法是链式承诺:

function findFirst() {
  return Entity.find(1).tap(function(result) {
    if (!result) throw new Error("no result");
  });
}

这样做更好,因为它更简单、更短、更易读、更不容易出错。

【讨论】:

  • 感谢您的简短回复。我只需要注意您提到的文章(错误代码与异常)-如果您仔细阅读它,您会发现异常有很多缺点,并且它们“应该被所有编写低质量代码的懒惰程序员所接受像我自己”。也许这只是一个意见问题。不过感谢您的回复!
  • @AndreyPopov 我认为你应该下basics,除非你在 NASA 工作,在这种情况下你应该使用错误代码
【解决方案2】:

每个帖子请只问一个问题:-)

.then(success, fail) 真的是反模式吗?我应该一直使用.then(success).catch(error)吗?

没有。 They just do different things,不过一旦知道了就可以选择合适的了。

如何使用promisify,它有什么好处?

我认为 Bluebird Promisification docs 很好地解释了这一点 - 它习惯于 convert a callback api 来返回承诺。

谈到 Promises/A+,拒绝 promise 的正确方法是什么——抛出错误?

是的,抛出错误完全没问题。 then 回调中,你可以抛出它,它会被自动捕获,导致结果 promise 被拒绝。

你也可以使用return @987654324@(new Error(…));;两者的效果完全一样。

我的一个朋友帮助了我,我使用了一个新的承诺来解决问题,就像这样:[…]

没有。 You really shouldn't use that。只需使用 then 并在其中抛出或返回一个被拒绝的承诺。

但如果我错过了 catch 语句 - 我的整个应用程序都会崩溃!

不,不会。请注意,.catch() 方法不是 try catch 语句 - 在调用 then 回调的地方已经捕获了错误。然后它只作为参数传递给catch 回调,.catch() 调用不需要捕获异常。

当您错过.catch() 时,您的应用程序不会崩溃。所有发生的事情都是你有一个被拒绝的承诺,你的应用程序的其余部分不会受此影响。你会得到一个unhandled rejection event,它可以是dealt with globally

当然,那不应该发生;每个承诺不是每个承诺实例)都应该以适当处理错误的.catch()结束。但是您绝对不需要在每个辅助函数中都使用.catch(),当它向其他地方返回一个承诺时,通常会由调用者处理错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-18
    • 2018-01-27
    • 2018-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-24
    • 2021-04-21
    相关资源
    最近更新 更多