Skip to content
This repository has been archived by the owner on Nov 9, 2023. It is now read-only.

Allow middleware to be async, deprecate createAsyncMiddleware #81

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

rekmarks
Copy link
Member

@rekmarks rekmarks commented Mar 27, 2021

This PR lets any middleware function be async, and deprecates createAsyncMiddleware. The readme has been updated.

Previously, we did not handle rejections of plain, async middleware functions. createAsyncMiddleware was created in part to solve this problem. This PR introduces a simple refactor of #runMiddleware to handle rejections by async middleware functions.

It accomplishes this using a deferred promise, which was one of the techniques used to implement createAsyncMiddleware. Meanwhile, createAsyncMiddleware has been updated in order to retain backwards compatibility.

This change should be fully backwards-compatible.

Copy link
Member

@Gudahtt Gudahtt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally seems promising! You really had me there with the title 😅 I thought you were getting rid of async middleware, but no, you're making all middleware optionally async.

I'd recommend leaving createAsyncMiddleware around in this PR and marking it as deprecated instead, to make migrating away from createAsyncMiddleware easier. We can remove it completely later on.

@kumavis
Copy link
Member

kumavis commented Apr 12, 2021

I'd recommend leaving createAsyncMiddleware around in this PR and marking it as deprecated instead, to make migrating away from createAsyncMiddleware easier.

i think bumping major here is sufficient, others will still pull in the older version with the middleware util

@kumavis
Copy link
Member

kumavis commented Apr 12, 2021

hmm, i actually dont like this.
the callback api and async api are different and shouldnt be mixed
better to add a engine.addAsyncMiddleware or just mandate that all middleware is async

@kumavis
Copy link
Member

kumavis commented Apr 12, 2021

create async middleware currently allows you to await next() and does not have an end fn. the end is implied when the async middleware completes without calling next

@rekmarks
Copy link
Member Author

the callback api and async api are different and shouldnt be mixed

@kumavis this is working up to resolving that problem. In #83 (dependency: #81 (this) -> #82 -> #83) I remove next callbacks completely in favor of a (req, res, end) signature where middleware can either be sync or async.

@rekmarks rekmarks force-pushed the remove-createAsyncMiddleware branch from aca7998 to 1d9f07c Compare May 18, 2021 00:38
@rekmarks
Copy link
Member Author

The main reason for removing next is that the async middleware pattern of await next(); /* do stuff */; return; invariably violates the node/callback-return ESLint rule. I agree with the authors of that rule that not returning the callback is an anti-pattern.

I decided to keep end in #83 because it seemed more explicit to call a function to end the processing of a request. That was purely an editorial decision, however, and we could instead decide to end the request whenever response.result or response.error are set, or when an error is thrown in a middleware.

* Handle errors fro masync middleware function rejections
* Update readme
* Update JsonRpcMiddleware type
* Prevent promise resolution function from being called twice
@rekmarks rekmarks force-pushed the remove-createAsyncMiddleware branch from 2cdcf45 to 378a820 Compare May 19, 2023 05:57
@rekmarks rekmarks changed the title Remove createAsyncMiddleware Allow middleware to be async, deprecate createAsyncMiddleware May 19, 2023
@rekmarks rekmarks requested a review from Gudahtt May 19, 2023 20:32
@@ -19,7 +19,7 @@ type ScaffoldMiddlewareHandler<
export function createScaffoldMiddleware(handlers: {
[methodName: string]: ScaffoldMiddlewareHandler<JsonRpcParams, Json>;
}): JsonRpcMiddleware<JsonRpcParams, Json> {
return (req, res, next, end) => {
return async (req, res, next, end) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter is asking for this, because middleware can now be async. We could create sync and async middleware types, not sure if it's worth it.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems fine. Make all the things async!

Copy link

@mcmire mcmire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @rekmarks, I know you've been waiting for movement on this. I've had this on my list for a while and finally took the time to review this. I definitely learned some things while reviewing this, like how async middleware resolve the request via a callback passed to next, and that those callbacks are processed in reverse order when all middleware has been processed. I haven't 100% fully grokked how that works, but it does seem purely based on how it's used internally that next is less of a necessity and more of an implementation detail, so I'm glad that we're removing it to make things simpler.

Anyway, I really have only one suggestion here, but I understand your changes enough to say that they're good. I will monitor this PR this week so we can finally merge it.

returnHandlers.push(returnHandler);
}

// False indicates that the request should not end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought for another PR: I'm curious whether it would make more sense to make this an object rather than an array so we can make this self-documenting:

return resolve({ error: null, isComplete: false });


try {
await middleware(request, response, next, end);
} catch (error: any) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Does it make more sense to use unknown rather than any here?

*
* The return handler will always be called. Its resolution of the promise
* enables the control flow described above.
*
* @deprecated As of version 7.1.0, middleware functions can be asnync. This
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo

Suggested change
* @deprecated As of version 7.1.0, middleware functions can be asnync. This
* @deprecated As of version 7.1.0, middleware functions can be async. This

}
returnHandlers.push(returnHandler);
}
let resolve: (value: [unknown, boolean]) => void;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see, the reason why we have to do this now is because the function that the Promise constructor takes can't itself be async? Good solution.

} else {
innerEnd(null);
}
} catch (error: any) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
} catch (error: any) {
} catch (error: unknown) {

@@ -19,7 +19,7 @@ type ScaffoldMiddlewareHandler<
export function createScaffoldMiddleware(handlers: {
[methodName: string]: ScaffoldMiddlewareHandler<JsonRpcParams, Json>;
}): JsonRpcMiddleware<JsonRpcParams, Json> {
return (req, res, next, end) => {
return async (req, res, next, end) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems fine. Make all the things async!

@MajorLift
Copy link
Contributor

This library has now been migrated into the core monorepo. This PR has been locked and this repo will be archived shortly. Going forward, releases of this library will only include changes made in the core repo.

  • Please push this branch to core and open a new PR there.
  • Optionally, add a link pointing to the discussion in this PR to provide context.

@MetaMask MetaMask locked and limited conversation to collaborators Nov 7, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants