我应该在异步函数中抛出错误或返回被拒绝的承诺吗?

时间:2017-08-03 16:52:16

标签: javascript node.js promise

我正在处理AWS JS SDK提供的承诺。当我创建包装AWS SDK的异步函数时,我所做的事情的主旨如下:

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.reject('failed')。说实话,我还没有那么精通承诺,所以他们的解释有点过了我的脑袋。

3 个答案:

答案 0 :(得分:3)

承诺/承诺链中的throw 会自动导致承诺/承诺链被拒绝。

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中,它在throw返回Promise之前使用sns.createTopic().promise()。要创建并返回已处于拒绝状态的承诺,您可以使用Promise.reject(new Error('failed'))代替throw

答案 1 :(得分:2)

他们是对的。

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关键字。通过使用此关键字,函数结果将自动包含一个承诺(类似于@peteb&#39的答案所示)。

答案 2 :(得分:0)

我不同意告诉你的人

  

永远不会在这些基于承诺的函数中引发错误

在我看来,你应该同步抛出TypeError(),因为它表示程序员错误而不是操作错误

引用Joyent | Error Handling

  

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

     

程序员错误 是程序中的错误。通过更改代码可以始终避免这些错误。它们永远不能被正确处理(因为根据定义,有问题的代码被破坏了。)

您的同事无法区分这些类型的错误,并且您编写的代码几乎应该是,除了使用通用Error()而不是语义上纠正TypeError()

你为什么要关心差异?

您最初是说您正在为AWS SDK编写包装器。因此,从使用您的库的开发人员的角度来看,您是否认为他们更喜欢调试一个程序,该程序会立即抛出他们做错的事情,或者他们更喜欢调试无声地失败的程序,试图解决他们滥用您的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
}

the chart here中可以找到Node.js运行时环境中用于区分这些错误的一些真实示例,即使在异步函数中也是如此:

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同步,并且当错误发生在错误内容时应该reject异步写程序。

这反映了Joyent的官方建议:

  

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