问题描述
当进行 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 一个未捕获的错误应该在调用树上进一步传递,直到它被捕获?
推荐答案
如果你正确地链接你的 Promise,你应该永远不会看到这个警告,你的所有错误都会被 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 解决了,我们就无法处理它的结果——如果它拒绝,我们就无法处理错误(它未处理,如警告所示).
一般来说,您应该始终确保您的 Promise 链接正确,如上所示.这使用 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 和 catch 的最后一句话
在处理 Promises 时,我们经常会看到函数调用后捕获的错误,例如:
try { await doSomething() } catch (error) { // handle error } return doSomething.catch((error) => { //handle error })
这在同步上下文中很重要(例如,在使用 express 构建 REST api 时).未能捕获被拒绝的 Promise 将导致熟悉的 UnhandledPromiseRejectionWarning.然而,因为 GraphQL 的执行层有效地作为一个巨大的 try/catch 运行,所以只要你的 Promise 被正确地链接/等待,就没有必要捕获你的错误.这是真的,除非 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()
问题描述
When making a GraphQL query, and the query fails, Apollo solves this by having a data-object and an error-object.
When an async error is happening, we get the same functionality with one data-object and one error-object. But, this time we get an UnhandledPromiseRejectionWarning too, with information about: 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..
So, we obviously need to solve this, but we want our async-functions to cast errors all the way up to Apollo. Do we need to try...catch all functions and just pass our error further up the tree? Coming from C#, were an exception just goes all the way to the top if never caught, it sounds like a tedious job to tell Apollo GraphQL that one (or more) leaves failed to retrieve data from the database.
Is there a better way to solve this, or is there any way to tell javascript/node that an uncaught error should be passed further up the call tree, until it's caught?
推荐答案
If you correctly chain your promises, you should never see this warning and all of your errors will be caught by GraphQL. Assume we have these two functions that return a Promise, the latter of which always rejects:
async function doSomething() { return } async function alwaysReject() { return Promise.reject(new Error('Oh no!')) }
First, some correct examples:
someField: async () => { await alwaysReject() await doSomething() }, // Or without async/await syntax someField: () => { return alwaysReject() .then(() => { return doSomething() }) // or... return alwaysReject().then(doSomething) },
In all of these cases, you'll see the error inside the errors array and no warning in your console. We could reverse the order of the functions (calling doSomething first) and this would still be the case.
Now, let's break our code:
someField: async () => { alwaysReject() await doSomething() }, someField: () => { alwaysReject() // <-- Note the missing return .then(() => { return doSomething() }) },
In these examples, we're firing off the function, but we're not awaiting the returned Promise. That means execution of our resolver continues. If the unawaited Promise resolves, there's nothing we can do with its result -- if it rejects, there's nothing we can do about the error (it's unhandled, as the warning indicates).
In general, you should always ensure your Promises are chained correctly as shown above. This is significantly easier to do with async/await syntax, since it's exceptionally easy to miss a return without it.
What about side effects?
There may be functions that return a Promise that you want to run, but don't want to pause your resolver's execution for. Whether the Promise resolves or returns is irrelevant to what your resolver returns, you just need it to run. In these cases, we just need a catch to handle the promise being rejected:
someField: async () => { alwaysReject() .catch((error) => { // Do something with the error }) await doSomething() },
Here, we call alwaysReject and execution continues onto doSomething. If alwaysReject eventually rejects, the error will be caught and no warning will be shown in the console.
Note: These "side effects" are not awaited, meaning GraphQL execution will continue and could very well finish while they are still running. There's no way to include errors from side effects inside your GraphQL response (i.e. the errors array), at best you can just log them. If you want a particular Promise's rejection reason to show up in the response, you need to await it inside your resolver instead of treating it like a side effect.
A final word on try/catch and catch
When dealing with Promises, we often see errors caught after our function call, for example:
try { await doSomething() } catch (error) { // handle error } return doSomething.catch((error) => { //handle error })
This is important inside a synchronous context (for example, when building a REST api with express). Failing to catch rejected promises will result in the familiar UnhandledPromiseRejectionWarning. However, because GraphQL's execution layer effectively functions as one giant try/catch, it's not really necessary to catch your errors as long as your Promises are chained/awaited properly. This is true unless A) you're dealing with side effects as already illustrated, or B) you want to prevent the error from bubbling up:
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()