如何正确处理异步错误?

时间:2019-03-06 14:39:39

标签: javascript node.js graphql apollo-server

进行GraphQL查询时,查询失败,Apollo通过具有数据对象和错误对象来解决此问题。

当发生异步错误时,我们可以使用一个数据对象和一个错误对象获得相同的功能。但是,这次我们也得到了UnhandledPromiseRejectionWarning,其中包含有关DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.的信息。

因此,我们显然需要解决此问题,但是我们希望异步函数将错误一直传递到Apollo。我们是否需要尝试...捕获所有功能,然后将错误进一步传递到树上?来自C#的一个例外是,如果从未捕获到一个异常,它会一直上升到顶端。要告诉Apollo GraphQL,一个(或多个)叶子无法从数据库中检索数据听起来很繁琐。

是否有更好的方法来解决此问题,或者有什么办法告诉javascript / node一个未捕获的错误应进一步传递到调用树,直到被捕获?

1 个答案:

答案 0 :(得分:1)

如果您正确地兑现了承诺,您将永远不会看到此警告,并且GraphQL会捕获所有错误。假设我们有这两个返回Promise的函数,后者总是拒绝:

async function doSomething() {
  return
}

async function alwaysReject() {
  return Promise.reject(new Error('Oh no!'))
}

首先,给出一些正确的例子:

someField: async () => {
  await alwaysReject()
  await doSomething()
},

// Or without async/await syntax
someField: () => {
  return alwaysReject()
    .then(() => {
      return doSomething()
    })
  // or...
  return alwaysReject().then(doSomething)
},

在所有这些情况下,您将在errors数组中看到错误,并且在控制台中没有警告。我们可以颠倒函数的顺序(首先调用doSomething),情况仍然如此。

现在,让我们破坏代码:

someField: async () => {
  alwaysReject()
  await doSomething()
},

someField: () => {
  alwaysReject() // <-- Note the missing return
    .then(() => {
      return doSomething()
    })
},

在这些示例中,我们关闭了该函数,但我们没有等待返回的Promise。这意味着我们的解析器将继续执行。如果未解决的Promise解决了,我们将无法处理其结果-如果它拒绝了,我们将无法对错误进行处理(警告未表明,该错误未得到处理)。

通常,您应始终确保正确地链接了Promises(承诺),如上所示。通过async / await语法,这非常容易实现,因为如果没有它,则很容易错过return

副作用如何?

可能有一些函数返回您要运行的Promise,但不想暂停您的解析程序的执行。 Promise解决还是返回与您的解决程序返回的内容无关,您只需要运行它即可。在这些情况下,我们只需要catch来处理被拒绝的承诺:

someField: async () => {
  alwaysReject()
    .catch((error) => {
      // Do something with the error
    })
  await doSomething()
},

在这里,我们调用alwaysReject,执行继续到doSomething。如果alwaysReject最终拒绝,错误将被捕获,并且控制台中不会显示任何警告。

注意:这些“副作用”没有被等待,这意味着GraphQL执行将继续,并且在它们仍在运行时可以很好地完成。没办法在GraphQL响应(即errors数组)中包含来自副作用的错误,充其量您只能记录下来。如果您希望在响应中显示某个Promise的拒绝原因,则需要在解析器中等待它,而不是将其视为副作用。

关于try / catch and catch的最后一句话

在处理Promises时,我们经常会在函数调用后看到错误,例如:

try {
  await doSomething()
} catch (error) {
  // handle error
}

return doSomething.catch((error) => {
  //handle error
})

这在同步上下文中非常重要(例如,在使用express构建REST api时)。未能兑现被拒绝的承诺将导致熟悉的UnhandledPromiseRejectionWarning。但是,由于GraphQL的执行层有效地充当了一个巨大的try / catch,因此只要正确地链接/等待了Promises,就没有必要捕获错误。除非A)您正在处理已经说明的副作用,或者B)要防止错误冒泡,否则这是正确的:

try {
  // execution halts because we await
  await alwaysReject()
catch (error) {
  // error is caught, so execution will continue (unless I throw the error)
  // because the resolver itself doesn't reject, the error won't be bubbled up
}
await doSomething()