如何正确处理异步错误?
How to handle async errors correctly?
当进行 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 应该将未捕获的错误进一步向上传递到调用树,直到它被捕获?
如果你正确地链接了你的承诺,你应该永远不会看到这个警告,你的所有错误都会被 GraphQL 捕获。假设我们有这两个函数 return 一个 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()
})
},
在这些示例中,我们触发了函数,但我们没有等待 returned Promise。这意味着我们的解析器会继续执行。如果未等待的 Promise 解决,我们无法对其结果做任何事情——如果它拒绝,我们就无法对错误做任何事情(它未处理,如警告所示)。
一般来说,您应该始终确保您的 Promise 已正确链接,如上所示。使用 async/await 语法要容易得多,因为没有它很容易错过 return
。
副作用呢?
可能有些函数 return 您想要 运行 的 Promise,但不想暂停解析器的执行。 Promise 是否解析或 returns 与你的解析器 returns 无关,你只需要它 运行。在这些情况下,我们只需要 catch
来处理被拒绝的承诺:
someField: async () => {
alwaysReject()
.catch((error) => {
// Do something with the error
})
await doSomething()
},
在这里,我们调用 alwaysReject
并继续执行到 doSomething
。如果 alwaysReject
最终拒绝,错误将被捕获并且不会在控制台中显示警告。
注意: 这些 "side effects" 没有等待,这意味着 GraphQL 执行将继续,并且很可能在它们仍在 运行ning 时完成。无法在 GraphQL 响应(即 errors
数组)中包含副作用引起的错误,最多只能记录它们。如果您希望特定的 Promise 拒绝原因出现在响应中,您需要在解析器中等待它,而不是将其视为副作用。
关于 try/catch 和 catch
的最后一句话
在处理 Promises 时,我们经常会看到函数调用后出现错误,例如:
try {
await doSomething()
} catch (error) {
// handle error
}
return doSomething.catch((error) => {
//handle error
})
这在同步上下文中很重要(例如,在使用 express 构建 REST api 时)。未能捕捉到被拒绝的承诺将导致熟悉的 UnhandledPromiseRejectionWarning
。但是,由于 GraphQL 的执行层有效地作为一个巨人 try/catch 发挥作用,只要您的 Promise chained/awaited 正确,就没有必要捕获您的错误。这是真的,除非 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()
当进行 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 应该将未捕获的错误进一步向上传递到调用树,直到它被捕获?
如果你正确地链接了你的承诺,你应该永远不会看到这个警告,你的所有错误都会被 GraphQL 捕获。假设我们有这两个函数 return 一个 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()
})
},
在这些示例中,我们触发了函数,但我们没有等待 returned Promise。这意味着我们的解析器会继续执行。如果未等待的 Promise 解决,我们无法对其结果做任何事情——如果它拒绝,我们就无法对错误做任何事情(它未处理,如警告所示)。
一般来说,您应该始终确保您的 Promise 已正确链接,如上所示。使用 async/await 语法要容易得多,因为没有它很容易错过 return
。
副作用呢?
可能有些函数 return 您想要 运行 的 Promise,但不想暂停解析器的执行。 Promise 是否解析或 returns 与你的解析器 returns 无关,你只需要它 运行。在这些情况下,我们只需要 catch
来处理被拒绝的承诺:
someField: async () => {
alwaysReject()
.catch((error) => {
// Do something with the error
})
await doSomething()
},
在这里,我们调用 alwaysReject
并继续执行到 doSomething
。如果 alwaysReject
最终拒绝,错误将被捕获并且不会在控制台中显示警告。
注意: 这些 "side effects" 没有等待,这意味着 GraphQL 执行将继续,并且很可能在它们仍在 运行ning 时完成。无法在 GraphQL 响应(即 errors
数组)中包含副作用引起的错误,最多只能记录它们。如果您希望特定的 Promise 拒绝原因出现在响应中,您需要在解析器中等待它,而不是将其视为副作用。
关于 try/catch 和 catch
的最后一句话在处理 Promises 时,我们经常会看到函数调用后出现错误,例如:
try {
await doSomething()
} catch (error) {
// handle error
}
return doSomething.catch((error) => {
//handle error
})
这在同步上下文中很重要(例如,在使用 express 构建 REST api 时)。未能捕捉到被拒绝的承诺将导致熟悉的 UnhandledPromiseRejectionWarning
。但是,由于 GraphQL 的执行层有效地作为一个巨人 try/catch 发挥作用,只要您的 Promise chained/awaited 正确,就没有必要捕获您的错误。这是真的,除非 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()