【问题标题】:Should I throw an error or return a rejected promise inside an async function? [duplicate]我应该在异步函数中抛出错误还是返回被拒绝的承诺? [复制]
【发布时间】:2020-01-04 06:40:47
【问题描述】:

我正在使用AWS JS SDK 提供的承诺。当我创建一个包装 AWS 开发工具包的异步函数时,我正在做的事情的要点如下所示:

module.exports.myCustomFunction = input => {

    if (badInput) {
        throw new Error('failed') // <-- This is they key line
    }

    return sns.createTopic(someParams).promise()
}

// The caller looks like this:
myModule.myCustomFunction(someInput).then(result => {
    // carry on
})
.catch(err => {
    // do something with the error
})

有人找我说我永远不应该在这些基于 Promise 的函数中抛出错误。他们建议改为返回Promise.reject('failed')。老实说,我还不太熟悉承诺,所以他们的解释有点让我头疼。

【问题讨论】:

标签: javascript node.js promise


【解决方案1】:

throw Promise/Promise 链将自动导致该 Promise/Promise 链被拒绝。

const myFunc = input => {
  return new Promise((resolve, reject) => {
    if (badInput) {
      throw new Error('failed')
    }

    return resolve(sns.createTopic(input).promise())
  })
}

return myFunc('some input')
  .then(result => { 
    // handle result
  })
  .catch(err => { console.log(err) }) // will log 'failed'

但是,您的 myCustomFunction 没有包装在 Promise 中,它在 sns.createTopic().promise() 返回 Promise 之前使用了 throw。要创建并返回已处于拒绝状态的 Promise,您可以使用 Promise.reject(new Error('failed')) 而不是 throw

【讨论】:

  • 你不能在return 构造函数回调中使用return
  • @Bergi 是的,我的错误,忘了把它包装在resolve() 中,我已经在我的答案中更新了这个。
【解决方案2】:

我不同意任何告诉你的人

永远不要在这些基于 Promise 的函数中抛出错误

在我看来,您应该TypeError()指示程序员错误而不是操作错误时同步抛出。 p>

引用Joyent | Error Handling

操作错误 代表正确编写的程序遇到的运行时问题。这些不是程序中的错误。 事实上,这些通常是其他问题 [...]

程序员错误 是程序中的错误。这些都是可以通过更改代码来避免的。它们永远无法正确处理(因为根据定义,相关代码已损坏)。

您的同事似乎无法区分这些类型的错误,而您编写的代码几乎应如此,除了使用通用的 Error() 而不是语义正确的TypeError().

为什么要关心差异?

您一开始说您正在为 AWS 开发工具包编写一个包装器。所以,从使用你的库的开发人员的角度来看,你认为他们更愿意调试一个在他们做错的地方立即抛出的程序,还是他们更愿意调试一个静默失败的程序,试图解决他们滥用你的 API 却没有通知他们错误的代码?

如果您认为第一个选项听起来更容易处理,那您就完全正确。在告诉程序员他们做错了什么时,您的程序应该始终尽可能透明。试图解决滥用问题会导致 API 出现错误,这些 API 具有未定义、未记录且只是简单的奇怪行为。

他们试图推荐我做什么?

举一个非常基本的例子(可能是不公平的比较,因为我不知道什么是 badInput),你的同事似乎在告诉你应该这样做:

try {
  if (badInput) {
    throw new Error('failed')
  }

  ...
} catch (error) {
  // expected error, proceed as normal
  // ...except not really, you have a bug
}

而不是这个:

process.on('uncaughtException', function (error) {
  // deal with programmer errors here and exit gracefully
})

if (badInput) {
  throw new Error('failed')
}

try {
  ...
} catch (error) {
  // deal with operational errors here and continue as normal
}

Node.js 运行时环境中的一些真实示例可以区分这些错误,甚至在异步函数中可以在 the chart here 中找到:

Example func | Kind of func | Example error  | Kind of error | How to   | Caller uses
             |              |                |               | deliver  |
==========================================================================================
fs.stat      | asynchronous | file not found | operational   | callback | handle callback
             |              |                |               |          | error
-------------+--------------+----------------+---------------+----------+-----------------
fs.stat      | asynchronous | null for       | programmer    | throw    | none (crash)
             |              | filename       |               |          |

结论

我会让您决定您的特定问题是由于程序员错误还是操作错误造成的,但总的来说,给您的建议不是合理的建议,并鼓励有错误的程序试图就好像没有错一样继续进行。

TL;DR

在操作条件下预期返回Promise 的任何函数应在错误是由于错误引起时同步throw,并且在正确编写的程序中出现exogenous error 时应异步reject

这反映了Joyent的官方推荐:

从程序员错误中恢复的最佳方法是立即崩溃。

【讨论】:

  • 问题不是关于操作错误和程序员错误之间的区别,或者如何使用它们,问题是关于在承诺中使用什么:抛出错误或拒绝承诺。
  • @alexmac 这是绝对关于操作与程序员错误的。问题是我应该在异步函数中抛出错误还是返回被拒绝的承诺? 给出的建议是总是 拒绝。答案是抛出程序员错误,拒绝操作错误,反馈是给出的建议不合理。
  • @alexmac 查看我的更新,我添加了另一个相关示例,说明 Node.js 本身如何区分错误类型。
  • I agree with your stance,但是我们不知道输入错误是由于程序员错误还是预期的操作错误。现实情况是,most exceptions get caught 无论如何都会在某些then 回调中出现,所以它并没有太大的区别——应用程序不再因程序员错误而崩溃,除非您在catch 子句中明确区分操作错误。所以最好返回一个被拒绝的承诺,让每个人都开心,并迫使他们使用适当的错误处理。
  • @Bergi 这就是为什么我在结论中留下“你弄清楚它是什么类型的错误”的原因,至于第二点,这很不幸,这就是为什么你使用非泛型 @987654339 @'s 这样一个简单的instanceof 检查就会告诉你是继续还是崩溃。
【解决方案3】:

他们是正确的。

myCustomFunction 的调用假定始终返回一个承诺(.then.catch 分别处理已解决和拒绝的承诺)。当您抛出错误时,该函数不会返回承诺。

可以使用它来捕获错误:

try {
  myModule.myCustomFunction(someInput).then(result => {
    // carry on
  })
  .catch(err => {
    // do something with the error
  })
} catch(err) {
  ...
}

但正如您所见,这会导致两个错误处理程序:try/catch 用于同步抛出的错误,.catch 用于sns.createTopic(someParams) 可能返回的任何被拒绝的承诺。

这就是为什么最好使用Promise.reject()

module.exports.myCustomFunction = input => {

    if (badInput) {
        return Promise.reject('failed');
    }

    return sns.createTopic(someParams).promise()
}

然后,.catch 将捕获这两种类型的错误/拒绝。

注意:对于较新版本的 Node.js(我相信是 v7.6 及更高版本),以下内容也可以使用:

module.exports.myCustomFunction = async input => {

    if (badInput) {
        throw new Error('failed');
    }

    return sns.createTopic(someParams).promise()
}

这里的关键是async 关键字。通过使用这个关键字,函数结果会自动被一个 Promise 包装起来(类似于@peteb 的答案所显示的)。

【讨论】:

  • 这也是您解决程序员错误的方式吗?这对于 API 调用来说是一种糟糕的方法,因为它会导致将无效输入当作有效输入来处理,只是将其视为任何操作错误。
  • @PatrickRoberts 我不知道input 的来源,而且也不能总是在其他代码中进行区分。即使它会抛出一个错误,你的论点在async(如关键字)函数的情况下也是没有意义的。
  • 我指的是你的第一个例子
  • @PatrickRoberts 你的意思是返回Promise.reject()?同样,我不知道input 来自哪里。
  • 重点是,类型断言是程序员错误,而不是操作错误。不管它来自哪里,如果输入类型错误,你就会崩溃。
猜你喜欢
  • 2019-07-03
  • 2021-10-17
  • 1970-01-01
  • 1970-01-01
  • 2019-03-27
  • 1970-01-01
  • 1970-01-01
  • 2020-03-22
  • 2015-08-12
相关资源
最近更新 更多