node.js - 为什么这些 promise 的拒绝是全局性的?

标签 node.js mongodb firebase async-await unhandled-promise-rejection

我们在 NodeJS 中有一个相当复杂的代码库,它同步运行大量 Promise。其中一些来自 Firebase (firebase-admin),一些来自其他 Google Cloud 库,一些是本地 MongoDB 请求。该代码大部分工作正常,在 5-8 小时内实现了数百万个 promise 。

但有时我们会因为网络超时等外部原因而被拒绝。因此,我们在所有 Firebase 或 Google Cloud 或 MongoDB 调用周围都有 try-catch block (这些调用是 awaited,因此被拒绝的 Promise 应该被 catch block 捕获)。如果出现网络超时,我们就过一段时间再试一次。这在大多数情况下都非常有效。有时,整个事情进行起来没有任何真正的问题。

但是,有时我们仍然会遇到未处理的 promise 被拒绝,然后出现在 process.on('unhandledRejection', ...) 中。这些拒绝的堆栈跟踪如下所示,例如:

Warn: Unhandled Rejection at: Promise [object Promise] reason: Error stack: Error: 
    at new ApiError ([repo-path]\node_modules\@google-cloud\common\build\src\util.js:59:15)
    at Util.parseHttpRespBody ([repo-path]\node_modules\@google-cloud\common\build\src\util.js:194:38)
    at Util.handleResp ([repo-path]\node_modules\@google-cloud\common\build\src\util.js:135:117)
    at [repo-path]\node_modules\@google-cloud\common\build\src\util.js:434:22
    at onResponse ([repo-path]\node_modules\retry-request\index.js:214:7)
    at [repo-path]\node_modules\teeny-request\src\index.ts:325:11
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

这是一个与我自己的代码完全分离的堆栈跟踪,所以我完全不知道在哪里可以改进我的代码以使其对错误更加健壮(错误消息似乎也非常有帮助)。

另一个例子:

Warn: Unhandled Rejection at: Promise [object Promise] reason: MongoError: server instance pool was destroyed stack: MongoError: server instance pool was destroyed
    at basicWriteValidations ([repo-path]\node_modules\mongodb\lib\core\topologies\server.js:574:41)
    at Server.insert ([repo-path]\node_modules\mongodb\lib\core\topologies\server.js:688:16)
    at Server.insert ([repo-path]\node_modules\mongodb\lib\topologies\topology_base.js:301:25)
    at OrderedBulkOperation.finalOptionsHandler ([repo-path]\node_modules\mongodb\lib\bulk\common.js:1210:25)
    at executeCommands ([repo-path]\node_modules\mongodb\lib\bulk\common.js:527:17)
    at executeLegacyOperation ([repo-path]\node_modules\mongodb\lib\utils.js:390:24)
    at OrderedBulkOperation.execute ([repo-path]\node_modules\mongodb\lib\bulk\common.js:1146:12)
    at BulkWriteOperation.execute ([repo-path]\node_modules\mongodb\lib\operations\bulk_write.js:67:10)
    at InsertManyOperation.execute ([repo-path]\node_modules\mongodb\lib\operations\insert_many.js:41:24)
    at executeOperation ([repo-path]\node_modules\mongodb\lib\operations\execute_operation.js:77:17)

至少这个错误消息说明了一些事情。

我的所有 Google Cloud 或 MongoDB 调用都有 awaittry-catch block (并且 MongoDB 引用在 catch 中重新创建) block ),因此如果 Promise 在这些调用中被拒绝,错误将被捕获在 catch block 中。

Firebase 库中有时会出现类似的问题。一些被拒绝的 Promise(例如由于网络错误)被我们的 try-catch block 捕获,但有些则没有,并且我无法改进我的代码,因为在这种情况下没有堆栈跟踪。

现在,无论这些问题的具体原因如何:我发现非常令人沮丧的是,这些错误只是在全局范围内发生(process.on('unhandledRejection', ...),而不是在我的代码中的某个位置,我可以使用 try-catch 来处理它们。这使我们损失了很多时间,因为当我们进入这种状态时,我们必须重新启动整个过程。

如何改进我的代码,使这些全局异常不再发生?当我在所有 promise 周围都有 try-catch block 时,为什么这些错误会出现全局未处理的拒绝?

这些可能是 MongoDB/Firebase 客户端的问题:但是,不止一个库受到此行为的影响,所以我不确定。

最佳答案

a stacktrace which is completely detached from my own code

是的,但是您调用的函数是否具有针对 IT 所做的正确错误处理?
下面我展示了一个简单的示例,说明为什么使用 try/catch 的外部代码不能阻止 promise 拒绝

//if a function you don't control causes an error with the language itself, yikes

//and for rejections, the same(amount of YIKES) can happen if an asynchronous function you call doesn't send up its rejection properly
//the example below is if the function is returning a custom promise that faces a problem, then does `throw err` instead of `reject(err)`)

//however, there usually is some thiAPI.on('error',callback) but try/catch doesn't solve everything
async function someFireBaseThing(){
  //a promise is always returned from an async function(on error it does the equivalent of `Promise.reject(error)`)
  //yet if you return a promise, THAT would be the promise returned and catch will only catch a `Promise.reject(theError)`
  
  return await new Promise((r,j)=>{
    fetch('x').then(r).catch(e=>{throw e})
    //unhandled rejection occurs even though e gets thrown
    //ironically, this could be simply solved with `.catch(j)`
    //check inspect element console since stackoverflow console doesn't show the error
  })
}
async function yourCode(){
  try{console.log(await someFireBaseThing())}
  catch(e){console.warn("successful handle:",e)}
}
yourCode()

再次阅读您的问题后,您似乎可以为任务设置一个时间限制,然后如果需要太长时间,则手动抛出到您等待的catch (因为如果错误堆栈不包含您的代码,则显示给 unhandledRejection 的 Promise 可能一开始就不会被您的代码看到)

function handler(promise,time){ //automatically rejects if it takes too long
  return new Promise(async(r,j)=>{
    setTimeout(()=>j('promise did not resolve in given time'),time)
    try{r(await promise)} catch(err){j(err)}
  })
}
async function yourCode(){
  while(true){ //will break when promise is successful(and returns)
    try{return await handler(someFireBaseThing(...someArguments),1e4)}
    catch(err){yourHandlingOn(err)}
  }
}

关于node.js - 为什么这些 promise 的拒绝是全局性的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71377792/

相关文章:

javascript - yarn 与 Npm - "works on my machine"- 澄清?

mongodb string 比 float 占用的空间小

android - 如何从 firebase 存储中的下载链接获取文件名?

javascript - 如何创建 channel 然后找到ID

javascript - Mongodb - 按 id 和嵌入数组值搜索

mysql - 使用 Node.js 和 MySQL 时服务器响应出现 TypeError

ios - 如何禁用 Firebase pod 附带的 Google 登录?

angularjs - 使用 POST 方法发送时出现 JSON 格式错误

node.js - mongodb 和 geoWithin

firebase - FCM Go Admin SDK 提供 MismatchSenderId