何时拒绝/解决承诺

Posted

技术标签:

【中文标题】何时拒绝/解决承诺【英文标题】:When to reject/resolve a promise 【发布时间】:2013-06-22 00:38:12 【问题描述】:

我正在考虑什么时候我需要拒绝一个承诺。 我发现了一些关于这个主题的问题,但找不到正确的答案。 When should I reject a promise?

这篇文章 http://howtonode.org/6666a4b74d7434144cff717c828be2c3953d46e7/promises 说:

Resolve:成功的 Promise 是“resolved”,它调用正在等待的成功侦听器,并记住为附加的未来成功侦听器解析的值。分辨率与返回值相关。 Reject:遇到错误情况时,Promise 会被“拒绝”,它会调用正在等待的错误侦听器并记住为将来附加的错误侦听器拒绝的值。拒绝与引发的异常相关。

这是基本准则吗? 只有在发生异常时才拒绝承诺?

但如果是这样的功能

findUserByEmail()

我希望该函数返回一个用户,这样我就可以在不验证结果的情况下继续这个链

findUserByEmail()
    .then(sendWelcomeBackEmail)
    .then(doSomeNiceStuff)
    .then(etc..)

什么是最好的/常见的做法?

【问题讨论】:

据我了解,您对拒绝的描述可能不正确,如果您所说的“未来”是指“下游”。当错误处理程序运行时,返回值用于履行(而不是拒绝)后续承诺。因此,一旦错误处理程序运行,该错误就被视为已处理,并且在成功方面继续执行。换句话说,如果你想在链中的连续步骤中处理错误,第一个错误处理程序将需要重新抛出它,以便调用链中的后续错误处理程序,就像 try/catch。 仅供参考,您引用的那篇文章有点不准确;它应该说“完成”时说“解决”。 【参考方案1】:

一般而言,您可以认为拒绝类似于同步throw,而履行类似于同步return。每当功能以某种方式不成功时,您都应该拒绝。这可能是超时、网络错误、输入错误等。

拒绝承诺,就像抛出异常一样,对控制流很有用。它不必代表真正的意外错误;它可以代表您完全预期和处理的问题:

function getProfile(email) 
  return getProfileOverNetwork(email)
    .then(null, function (err) 
      //something went wrong getting the profile
      if (err.code === 'NonExistantUser') 
        return defaultUser;
       else if (profileCached(email)) 
        return getProfileFromCache(email);//fall back to cached profile
       else 
        throw err;//sometimes we don't have a nice way of handling it
      
    )

拒绝让我们跳过正常的成功行为,直到我们找到一个知道如何处理它的方法。作为另一个例子,我们可能有一些深深嵌套在应用程序堆栈底部的函数,它会拒绝。这可能要到堆栈的最顶端才能处理,我们可以在其中记录它。关键是拒绝像同步代码中的异常一样在堆栈中向上传播。

一般来说,只要有可能,如果您正在努力编写一些异步代码,您应该考虑“如果这是同步的,我会写什么”。从它到承诺的等价物通常是一个相当简单的转换。

exists 方法中可能会使用被拒绝的 Promise 的一个很好的例子:

function exists(filePath) 
  return stat(filePath) //where stat gets last updated time etc. of the file
    .then(function ()  return true; , function ()  return false; )

请注意,在这种情况下,拒绝完全是意料之中的,只是意味着文件不存在。还要注意它是如何与同步函数并行的:

function existsSync(filePath) 
  try 
    statSync(filePath);
    return true;
   catch (ex) 
    return false;
  

你的例子

回到你的例子:

如果没有找到用户,我通常会选择拒绝来自findUserByEmail 的承诺。这是您完全期望有时会发生的事情,但它是规范的例外,并且应该与所有其他错误非常相似地处理。同样,如果我正在编写一个同步函数,我会在throw 一个例外。

有时只返回null 可能会有用,但这取决于您的应用程序逻辑,可能不是最好的方法。

【讨论】:

谢谢,正如预期的那样,这更像是某种哲学问题。因为您与在找不到用户时抛出的同步异常的比较也可以从双方争论。但是,在我看来,我同意你的结论。 是的,我想表达的观点是,无论是拒绝承诺还是抛出异常,都是同一个论点。 这个例子在技术上是正确的,但是我建议你不要使用异常来控制流。充其量,它会让其他程序员感到困惑。在最坏的情况下,它会使错误报告变得困难,并可能导致性能下降。您真的希望每次找不到用户时都生成堆栈跟踪吗?更多信息:c2.com @ben 这取决于很多事情。性能成本可能不是微不足道的,但只要找不到用户不是太频繁发生,就可以了。考虑到这是许多 javascript 库中的典型行为。比如node.js中的fs.exists函数就是通过调用fs.stat然后处理错误来实现的。 它唯一依赖的是抛出异常的外部库。如果您可以选择,与返回值相比,作为流控制的异常总是会更慢,并且不会添加任何内容。被普遍使用并不能使它成为一个好的模式。即便如此,我确实看到了很多证据表明它被普遍使用。【参考方案2】:

我知道你来自哪里。 Q 和 Q 文档可以很容易地让你相信延迟/承诺拒绝都是关于异常处理的。

不一定如此。

无论您的申请需要什么原因,都可以拒绝延期申请。

Deferreds/promise 都是关于处理来自异步进程的响应,每个异步进程都可能导致各种结果——其中一些是“成功的”,一些是“不成功的”。您可以选择拒绝您的延迟 - 无论出于何种原因,无论结果名义上是成功还是不成功,并且在 javascript 或异步过程中都没有抛出异常。

您还可以选择在异步进程上实现超时,在这种情况下,您可以选择拒绝延迟而未收到响应(成功或不成功)。事实上,对于超时,这是您通常会选择做的事情。

【讨论】:

是的,不幸的是,文档并没有真正给出任何用例的提示。谢谢你的回答。

以上是关于何时拒绝/解决承诺的主要内容,如果未能解决你的问题,请参考以下文章

承诺按顺序运行嵌套承诺并在第一次拒绝时解决

如何解决未处理的承诺拒绝?

处理多个承诺拒绝[重复]

等待但从未解决/拒绝承诺内存使用[重复]

有没有办法判断 ES6 承诺是不是被履行/拒绝/解决? [复制]

在 then 块中拒绝返回的承诺