-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Refactor to async-await (#521) #973
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
Conversation
@catarak I am working on working on all the controllers , Should I make separate prs or add commits here? |
I am currently having some linting error because of my global prettier settings, I will fix them at the end. |
ref #521 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, generally, here are some comments on what you have so far.
I'd suggest keeping the PRs small so they can be dealt with quicker.
res.json(updatedProject.files[updatedProject.files.length - 1]); | ||
}); | ||
} catch (err) { | ||
if (err) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If statement is not required. The return after the error message can also be removed.
.reduce((acc, childId) => ( | ||
[...acc, childId, ...getAllDescendantIds(files, childId)] | ||
), []); | ||
return parentFile.children.reduce( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just as a heads up, these stylistic changes should really not be included, it breaks the git blame for this line and is generally unhelpful when looking at diffs.
That said, making an additional commit to revert them would be worse in general, so only fix that if you're going to be doing it by altering commit history. I just thought I'd point out that it is something to look out for when you're creating a PR: If you have diffs on lines irrelevant to what you're doing, something usually has gone wrong :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My global prettier generally makes these changes , since the project does not has any local .prettierrc file , my global prettier kicks in and make the changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for this project, i squash commits for a PR, so i'm okay with you making commits to revert stuff. i also agree, these stylistic changes should not be included in this PR.
res.json(project.files); | ||
} catch (err) { | ||
// NOT SO GREAT ERROR HANDLING | ||
console.log(err); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably respond with a 500 error here so that the API user always gets a response - though don't send the user the error
return; | ||
} | ||
res.send(resolvedFile.content); | ||
}); | ||
} catch (err) { | ||
if (err) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another unnecessary if here & return below
} | ||
const newFile = updatedProject.files[updatedProject.files.length - 1]; | ||
updatedProject.files.id(req.body.parentId).children.push(newFile.id); | ||
updatedProject.save(innerErr => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be promisified too.
}); | ||
} catch (err) { | ||
get404Sketch(html => res.send(html)); | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can delete this return
Thanks for the review , I will make the changes! |
@meiamsome @catarak Should I make another pr when I am done refactoring other files? |
|
const filesToInject = files.filter(file => file.name.match(/\.(js|css)$/i)); | ||
injectMediaUrls(filesToInject, files, req.params.project_id); | ||
export async function serveProject(req, res) { | ||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm not a fan of the try
body being huge here. it's only here to catch errors from mongodb, right? so only line 13 should be in the body.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it also catches all errors that are unintended to make sure that the API user gets a response (500) when the server has a problem, instead of just having the connection hang. In non-async handlers this is handled by default by express because they try/catch all the handlers, but express doesn't support async
natively. There is express-async-handler which would help us wrap these functions correctly to catch those errors, but you're just moving where the wrapping handler is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait, i'm confused. are you saying that express doesn't support async/await
in the handler functions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's right, it doesn't natively handle it, it expects the user to either throw errors synchronously, or pass them all to next()
manually. Indeed if you look at the source of that async
wrapper, it is essentially just doing this large try
/catch
there. It's a shame that express doesn't understand Promises, but the people behind express decided it'd be too dramatic a change for express to support them. (As an aside they actually decided to redesign express entirely and made Koa which is great but differs in some ways.)
So, that leaves you with two options:
- Don't use
async
/await
and lose the great syntactical bonuses that gives you, keeping in mind that you still need to handle the asynchronous errors in your promise chains by eventually passing them tonext()
:
function (req, res, next) {
// Something synchronous here
promise
.then(() => doSomething())
.catch(next);
}
- Do use
async
/await
and be stuck with wrapping each route's handler in some sort of wrapper, be that a library one or just a bigtry
/catch
. The standardtry
/catch
is something akin to:
async function (req, res, next) {
try {
// Really long function here
} catch(e) {
next(e);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wow, okay. i had no idea! thanks for the detailed explanation. my instinct is to use the express-async-handler middleware, as wrapping everything in a try/catch feels like it gets redundant fast. just to make sure i totally understand this, say the middleware were included in the project, and i wanted to catch errors specific to the mongodb query/connection. that would look like
let project;
try {
project = await Project.findById(req.params.project_id);
} catch(e) {
// do something with the error, e.g. return 404 or 500
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ganeshpatro321 thanks for the update! that's super frustrating. i'm starting to feel like nested callbacks is better than this mess...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only problem with callbacks is not nesting or callbacks hell , I think if not now , we should definitely consider this as an important task in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@siddhant1 i agree! i'm not a huge fan of the nested callbacks. however, it feels like the tools to move everything to async/await are in flux, and i think it's best to wait a little bit.
one option could be to just move to promises. but, honestly, and maybe this is just me, but i'm not sure the promise syntax is too much cleaner/easier to read without async/await
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's important to note that one of the major benefits of using Promises/(async/await) is the ability to have errors bubble up naturally, rather than not being sure whether some errors may just be being silently ignored instead of raised correctly. As discussed before, Koa is a viable Express replacement that is functionally identical except for supporting promises natively. I would be happy to do an exploration PR into swapping out for that if you think it makes sense to have a look at it @catarak ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that is a great point.
it seems like koa is a fairly radical departure from express, and would involve some major changes. for me, there are higher priority issues, so working on this change would have to be fairly self-directed. however, i am happy to test and give support when i can. if you think there's a possibility of significantly improved error-handling and major code clean up, it's definitely worth investigating!
res.json({ success: false }); | ||
return; | ||
export async function createFile(req, res) { | ||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same issue here. i don't like the giant try
body.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense , I will refactor
); | ||
const newFile = updatedProject.files[updatedProject.files.length - 1]; | ||
updatedProject.files.id(req.body.parentId).children.push(newFile.id); | ||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
especially nested try/catch... i would try to avoid it if possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any way to avoid them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@catarak @siddhant1 I have a suggestion that can clear up this try catch litter. We can define a wrapper function to catch an error and wrap every async function with it. This way we can avoid try/catch with async await.
/* Helper buddy for removing async/await try/catch litter.
We can make the name of this function a little smaller */
function catchError(promise) {
return promise.then(data => {
if (data instanceof Error) return [data]
return [null, data]
}).catch(err => [err])
}
// Example Usage
async function usageExample(params) {
const [ err, data ] = await catchError(myPromise(params)) // We are using the wrapper here
if (err) {
// handle error
}
// Do stuff with data
}
What do you think about it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmmmm, i prefer express-async-handler
as i like that it handles the error handling as middleware. to me, it feels like a cleaner solution, rather than adding a helper function to every single controller function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Express-async-handler is good
I will refactor it along with adding a better error handling approach , because for now we are just logging the errors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i recommend not just pushing ahead on something. i think there are a few different options here and i'd like to see a comparison of these options, so we can make a decision about what the best path forward is.
closing this PR due to inactivity, but making sure this conversation is linked to in #521. |
I have verified that this pull request:
npm run lint
)Fixes #521