-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adapter Method Promise Rejections are Unhandled, causes Unreliable Upstream Promise Resolutions #1832
Comments
I have some opinions on this matter, and ultimately I agree with you, and I think we could do a much better job at error handling. Eg:
It also touches on a subject that I think we shouldn't mix Promises with async/await code, because it gets harder to work with it, and causes confusion for our users. I've previously talked about this in other issues, #731, #891 are good examples. The adapter code exceptionall stands out in this, and it would be awesome to improve, but I think they have to be improved in their new alternatives over at the other repo https://github.com/nextauthjs/adapters. Right now the Prisma one looks the closest to what I think we should aim for: https://github.com/nextauthjs/adapters/blob/canary/packages/prisma/src/index.ts Personally, I haven't been in a codebase for a very long time where I had to rely on a single My views on this might conflict with others on the team though, and that's why I haven't been doing much around this yet. Getting feedback on this from the community helps to prove my point though, so thanks for opening this! |
I’m really thankful to see this issue and the proposals of @balazsorban44. I couldn’t find out why NextAuth endpoints timed out and really glad to see this issue being explored in so much detail.
Making functions async unnecessarily and using methods like Besides that, I also feel that it’s unnecessary to transpile async functions for modern node versions. I would love to see reducing the usage of the |
https://github.com/typescript-eslint/typescript-eslint/blob/v4.22.0/packages/eslint-plugin/docs/rules/return-await.md Might be a good addition to this discussion |
This has been causing a lot of server errors in production for our company lately. May we try refactoring the codebase to fix these issues for people using adapters? |
The issue seems to happen every time a session expires, if that could help investigating the bug further. |
Thank you for your responses! I ultimately agree that an My only question is; does refactoring the return style of the adapters help with the unhandled next-auth/src/server/routes/signin.js Line 42 in 17b7898
Will You will have to forgive me for my lack deep understanding on this matter. I hope some of this makes sense. I appreciate your time! Cheers, |
@WillTheVideoMan slightly off-topic, but I see you have a custom adapter for FaunaDB. we have a built-in one over at https://github.com/nextauthjs/adapters/tree/canary/packages/fauna feel free to contribute to that if you think it needs improvements! (We are working on better out-of-the-box TS support à la a better |
@balazsorban44 For sure, I have already made some small changes in collaboration with others and will certainly contribute them towards the adapter as soon as is possible. Looking forward to making Fauna a first-class adapter, and will use the new Prisma adapter for structural guidance. |
#1871 Here is my take. We could take away the responsibility from the adapters to implement proper error handling. Currently just playing with this idea, I don't think it is there yet, feedback is welcome. |
#1871 Looks and feels great, and will go a long way to help adapter authors to feel confident that error handling has been approached and implemented already. Awesome! My only thought is that I am not sure how far this goes to solving the heart of the issue, which is the actual handling of errors from within the core itself. Whilst I think lifting the handling out of the adapter directly is a super idea, there are still instances within the core where a simple nullish comparator is used in places which could benefit from more explicit handling e.g. next-auth/src/server/routes/signin.js Line 42 in 17b7898
I have sketched out the following to try and illustrate my point. I have used your proposed new error handling methods here in combination with two 'core' functions which represent how the core might interact with adapters. Note how the // Returns a promise which will reject on demand.
const databaseQuery = (reject) => {
return new Promise(function (resolvePromise, rejectPromise) {
if (reject) rejectPromise(new Error("Database Promise Reject"));
else resolvePromise({ data: "useful" });
});
};
// A private adapter method with no error handling.
const _adapterMethod = async () => {
// Toggle `true` and `false` to control the dummy behaviour.
const data = await databaseQuery(true);
return data;
};
// An adapter method error handling wrapper funciton. *This idea is so great*!
const adapterMethodErrorHandler = () => {
return async function adapterMethod(...args) {
try {
return await _adapterMethod(...args);
} catch (error) {
throw new Error(error);
}
};
};
/**
* Here, the core code uses the existing unwrapped `await` style where a
* nullish comparison determines the value of `data`.
*
* However, when a database error is thrown, `data` never receives
* its default shape.
*
* The error remains unhandled, we cannot describe what happens
* to `data`.
*/
const coreCodeNoHandle = async () => {
const adapterMethod = adapterMethodErrorHandler();
const data = (await adapterMethod()) || { data: "default" };
console.log(data);
};
/**
* Here, the error-wrapped adapter method is further wrapped in a
* `try/catch` directy within the core.
*
* Here, in the event of an error thrown, the value of
* `data` can be explicitly set.
*
* We get to determine the value of data, and proceed accordingly.
* Further, we can directly control what happens next e.g. API route
* return shapes, redirects, etc...
*/
const coreCodeTryCatch = async () => {
const adapterMethod = adapterMethodErrorHandler();
let data;
try {
data = await adapterMethod();
} catch (error) {
data = { data: "default" };
}
console.log(data);
};
// Comment out the funcitons to see their differing styles.
coreCodeNoHandle();
//coreCodeTryCatch(); Maybe I am thinking too deeply on this, and such changes are not vital. To me, it could potentially allow |
@WillTheVideoMan I love that idea! Fallbacks could greatly improve the experience of end-users, although I think that issues like that should be logged as warnings via |
@balazsorban44 @kripod This is so great! I will pull down the latest canary release and see how things are working out. I am incredibly grateful that you both are receptive and proactive to these kinds of issues, where elsewhere they may go unnoticed for months, or stall whilst awaiting some corporate approval. This is a true open-source mindset, and can only be positive for the package and the NextJS ecosystem as a whole. Thank you for your time and energy. It is greatly valued. |
This is now in |
Just wanted to give a heads up, I am backporting this error handling to the built-in Follow along |
@balazsorban44 All looks good to me! Glad the error handling is lifted up and out of the adapters, a pattern I love to see! I think this solves the unhandled exceptions issue I was facing, so from my perspective the issue can be closed. Thanks again for your work. |
Hello everyone ! I feel like this issue is still here ! https://github.com/nextauthjs/next-auth/blob/main/src/adapters/error-handler.js
Obviously, adapter methods can still throw errors, and I thought the whole point of this discussion was to handle them in the core. Using the wrapper helps log issues, but then the error is still thrown and I really don't know where to catch those errors. I tried to wrap the Do you have any idea how to catch those unhandled promises ? Thank you :) |
Could you collaborate more? Which adapter/version/next-auth is this happening in? Had a quick look of some of our adapters, none of them read a |
Hello @balazsorban44, This comes from a custom Fauna Adapter, https://github.com/nextauthjs/adapters/blob/main/packages/fauna/src/index.js This is the function that throws : async getUserByEmail(email) {
if (!email) {
return null;
}
const FQL = q.Let(
{
ref: q.Match(q.Index(indexes.User), email),
},
q.If(q.Exists(q.Var('ref')), q.Get(q.Var('ref')), null),
);
const { ref, data: user } =
(await faunaClient.query<UserQueryResult>(FQL)) || {};
if (!user) {
return null;
}
const result = {
...user,
id: ref.id,
emailVerified: user.emailVerified
? new Date(user.emailVerified.value)
: null,
createdAt: new Date(user.createdAt.value),
// updatedAt: new Date(user.updatedAt.value), this one is crashing
updatedAt: user.updatedAt?.value
? new Date(user.updatedAt.value)
: new Date(),
};
return result;
}, |
Thanks for the extra info. The adapter error wrapper doesn't actually stop errors from happening, it just wraps the methods so they get a more useful name to identify where the error happened. I guess it's still up to the adapter to make sure it doesn't throw an error, but we could wrap this piece of code so it returns the user to an error page as well: next-auth/src/server/routes/signin.js Line 50 in 75ca097
|
Wrapping the I had an other question though that is still not answered : #2417 Thanks :p |
Describe the bug
Promises that reject with errors from within adapter methods are in some cases unhandled, leading to
UnhandledPromiseRejectionWarning
and potentially leaving pending upstream promises that never resolve.An example of such an unhandled promise rejection is during the email sign-in flow. In this case, if the adapter method
getUserByEmail
rejects with an error, then anUnhandledPromiseRejectionWarning
is logged to thenode
console.In development, upstream promises (such as the one returned by the client
signIn
method whenredirect
isfalse
) that rely on thegetUserByEmail
promise resolving or rejecting are left indefinitely pending.In production, upstream promises are eventually rejected by virtue of a
500
response from the server as it deals with the unhandled rejection exception.Steps to reproduce
Either clone repro:
https://github.com/WillTheVideoMan/nextauth-repro-unhandled-promise-rejection-warning
Or:
next-auth
in a development environment with an adapter.next-auth/src/server/routes/signin.js
Line 42 in 17b7898
UnhandledPromiseRejectionWarning
warning in thenode
console.500
response.Expected behavior
Expect all promises which may reject to be handled explicitly.
I think the overall intention here should be to avoid unhandled promise rejections entirely. This would ensure errors more consistently cascade in predictable ways, and importantly, would move in line with the upcoming
node v15
revisions: nodejs/node#33021Screenshots or error logs
Additional context
Whilst ultimately the production environment is the one to worry about, enabling consistent error handling throughout NextAuth would reduce the disparity between development and production. I discovered this issue exactly because of the lack of promise fulfilment in development, and so I had no way to properly test my implementation of error handling. This is a make-or-break for our testing methodology. It should be that we get the same fundamental behavior in development as we do in production, independent of any intrinsic NextJS magic.
Feedback
The text was updated successfully, but these errors were encountered: