-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[eslint] add rule to forbid async forEach bodies #111637
[eslint] add rule to forbid async forEach bodies #111637
Conversation
cb2684a
to
9fb9c19
Compare
...ns/security/public/management/users/components/confirm_delete_users/confirm_delete_users.tsx
Show resolved
Hide resolved
9fb9c19
to
ee60f73
Compare
49e8f46
to
74adce4
Compare
74adce4
to
20495b0
Compare
…int-no-async-foreach
…int-no-async-foreach
@@ -92,5 +92,6 @@ module.exports = { | |||
], | |||
|
|||
'@kbn/eslint/no_async_promise_body': 'error', | |||
'@kbn/eslint/no_async_foreach': 'error', |
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 believe we should expand the rule to cover other expressions, like this one #98463 (comment)
I'm not sure whether no-floating-promises implements the same check, but we can give it a 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.
Yeah, I'll look at a similar solution for that. I don't want to overload this rule though and have a special error message for that scenario.
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 don't want to overload this rule though and have a special error message for that scenario.
Would you suggest implementing a custom rule for every use-case when (...args: any[]) => Promise<any>
is used instead of (...args: any[]) => void
?
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 do think that having custom error messages for each situation would be nice, though I'm not convinced there are that many scenarios right now. It's possibly a bigger more wide issue than I'm thinking though.
return ( | ||
node.callee.type === esTypes.MemberExpression && | ||
node.callee.property.type === esTypes.Identifier && | ||
node.callee.property.name === 'forEach' && |
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.
unfortunately, it doesn't cover cases when a promise is returned without an async
keyword 😞
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, in that case we're creating a promise which can be handled, so I feel like it's less of an issue.
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, in that case we're creating a promise which can be handled
Could you elaborate on it? Sorry, I'm not follow who handles a promise in this case 😞
async function doAsync () {}
[].forEach(doAsync)
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.
Ah, I figured you were describing:
array.forEach(() => new Promise((resolve) => ...))
There's a chance we could improve this to use types for locally defined functions, but the more research I do on other things the more I feel like we need to start providing ESLint with full types... Which has a lot of complications.
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.
Alerting changes LGTM!
Pinging @elastic/fleet (Team:Fleet) |
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.
Security changes LGTM!
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 timelion one scares me a bit, but I think this comment here
// In theory none of the args should ever be promises. This is probably a waste. |
Tested and didn't notice any breakages, LGTM.
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.
Uptime Changes Looks Good !!
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.
Changes to Rollup telemetry collector LGTM, but this code is really the domain of @elastic/kibana-vis-editors since it's about visualizations that use rollups.
x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.ts
Show resolved
Hide resolved
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.
LGTM,
- Looked over 1 section of code, left one comment
- Talked about it with the internal team.
💚 Build Succeeded
Metrics [docs]Async chunks
Page load bundle
History
To update your PR or re-run it, just comment with: |
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.
maps/*
changes lgtm
🙇 asyncMap
, so much cleaner.
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.
Fleet tests 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.
Ok for the core changes. I don't want to block this PR. The core team can refactor the tests in @kbn/std
later.
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.
bfetch code changes LGTM.
💔 Backport failed
To backport manually run: |
Co-authored-by: spalger <spalger@users.noreply.github.com> # Conflicts: # x-pack/plugins/event_log/server/es/init.ts
I noticed an unresolved promise rejection on CI and tracked it back to a usage of
array.forEach()
which was passed an async function. Like #110349 this creates hidden promises which can't be tracked, preventing the rejections from being handled. To prevent this mistake I've implemented an ESLint rule to prevent devs from accidentally using this pattern.Unlike in the other PR I can't think of a lowest-common-denominator fix to implement across the board, so I have instead fixed all the violations manually. The fixes use one of three new helpers added to
@kbn/std
:asyncForEach(iter, fn)
: The most common replacement was to just replacearray.forEach()
with a helper that does the same thing. This callsfn
synchronously for each item inarray
and waits for the returned promise/observables to resolve/reject/complete/error.asyncMap(iter, fn)
: A common usage of asyncforEach()
was to add items to an array within scope. I have replaced those withasyncMap()
which callsfn
synchronously for each item inarray
and waits for the returned promise/observables to resolve/reject/complete/error. The result array has each item in the order of the source array.map$(iter, fn)
: A simple wrapper around RxJS for creating a stream from an iterable value where each item in the result is the return value of calling the map function with each item from the iterable. This is the basis of the other functions.No need for
async
: There were a handful of places where the function passed to.forEach()
wasasync
unnecessarily.Use a library: In a few places we could just replace the existing logic with a dependency which is already available and does the intended logic without any compromises.
Each helper implemented in this PR includes a variant which accepts a limit parameter between the iterable and the map function. I'm interested in having a discussion about if we should be using these instead of starting each concurrent iteration at the same time, potentially creating a lot more load from a single request on the server, or doing a lot more work on each frame in the UI. It's also possible that several places where I updated code take input from users which are variable in length and could be very long. In those cases I think it would make sense to limit the amount of work we are doing concurrently... idk, I originally wrote the "unlimited" helpers to have a default concurrency of 4 but got sheepish. For now the
*WithLimit()
versions are unused outside of the unlimited helpers, so if reviewers request I will remove them.I could also imagine us requiring usage of such helpers instead of doing
Promise.all(array.map())
which we do in many, many places. It could still be possible with something likeasyncMapWithLimit(iter, Infinity, fn)
but would require opting into this when the dev knows it won't lead to too much concurrency.