-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Rule proposal: functions should not be async
unless they await
#9284
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
Comments
Hi @benmccann!
|
Do you have specific examples in mind? I agree that the docs are not good and the rule has a lot of edge cases (I was recently working on this and... I don't really want to think deeply about all the edge cases that it would have failed on). However I think the rule generally is a good idea since it prevents Zalgo. |
It doesn't quite do what I'm describing, unfortunately. It doesn't check that a
However, if you get rid of the The rule I'm describing shouldn't trigger on such a function. If you remove the
Yes, the one in the issue description is the one we examined most closely. There's a link there to the Svelte codebase in case you want to see the code in context. If it's helpful to have more examples, I believe that for all of the places it suggested adding
I'm not sure what that means. |
Ah, ok, thanks for clarifying! I misunderstood the details there.
I'm not familiar with the performance implication. Are you aware of a reference for that? There is a slight benefit behaviorally for the rejection case, though, see function promiseFunction() {
if (Math.random() < 0.3) {
throw new Error('thrown error');
} else if (Math.random() < 0.5) {
return Promise.resolve('resolved value');
} else {
return Promise.reject('rejected error');
}
}
async function asyncFunction() {
if (Math.random() < 0.3) {
throw new Error('thrown error');
} else if (Math.random() < 0.5) {
return Promise.resolve('resolved value');
} else {
return Promise.reject('rejected error');
}
}
function callAsyncFunction() {
// this will never throw an exception.
// All errors will be logged to the console in the rejection handler.
asyncFunction().then(console.log, console.error);
// a third of the time exceptions will be synchronously caught
// a third of the time an exception will be logged in the rejection handler.
try {
promiseFunction().then(console.log, console.error);
} catch (e) {
console.error(e);
}
} EDIT - slight typos in the code |
I am also curious, under the premise that the async function promiseOrValueFunction(): Promise<number> {
if (Math.random() < 0.5) {
return 3;
} else {
return Promise.resolve(4);
}
} to function promiseOrValueFunction(): Promise<number> {
if (Math.random() < 0.5) {
return Promise.resolve(3);
} else {
return Promise.resolve(4);
}
} rather than let the linter allow that case? (which, you could use the base rule to achieve. You'd have to manually wrap the value returns, but an explicit return type would solve that problem for you) |
I am also curious to know what this means lol 😆 |
I can't find a reference for it, but three separate Svelte maintainers who are all better versed in JS than myself agreed. Some excerpts from conversations about it below. this will result in four promises being created rather than two:
and the evidence is where it gets shotty, cuz it's V8 source and as soon as you start relying on V8 behavior/characteristics it's "read the source" or "wait for v8 blog post"
Ah, maybe the base rule is preferable here actually to what I was proposing. Though that also makes me wonder if the |
Yeah, the reason I mention a reference is that claims around performance remind me of this, related issue for one of the eslint base rules, eslint/eslint#18166, which is... controversial at best 🙂. And my guess is that that would be relatively unlikely to be a deciding factor in rule logic unless there were a pretty extreme difference.
Hope this has been helpful in addressing your request! 🙂
Eh, even if we granted for the sake of argument that the behavior of the base rule were preferable, the extension uses type information, so what you'd have then is just a significantly-harder-to-configure, worse version of the base rule. Probably would make more sense to just document it as "when not to use" and remark that some find the base rule preferable. |
yes, thank you!!
In general that's super valuable, but for this particular rule I'm not sure the type information can be used in a way that results in a more valuable rule
Why would it be harder to configure? The version in this repo uses the options from the base rule, so it seems like the configuration is the same in either case |
I just mean, configuring typed linting in the first place is a hassle, since you have to supply the tsconfig and all that. It's much easier to get through the one-time-setup for an eslint core rule or non-type-aware rule. Though, of course, if you have any type-aware rules already successfully running, the marginal cost is zero for additional ones. |
not to mention potential runtime costs. Short version - if a rule can be written without type information, we significantly prefer to do so. |
From the perf side of things - people very much overstate the impact. Removing the I fully understand the style and the want to be as efficient as possible! But it's not really a perf concern. |
Ah, I think there was some confusion because when I said "though that also makes me wonder if the @typescript-eslint/require-await behavior should just be that of the base rule by default" then I was really asking if we even need But yeah, I think the typescript version actually is better here in most cases as it's less restrictive in how the code is written. In the Svelte codebase there's very little difference between the two rules so I didn't appreciate that initially, but I tried applying the base rule in another project and some places it was a little annoying to have to switch the code in places where performance doesn't matter like tests. And of course even in the main code it's admittedly a micro-optimization. So thanks for talking through this and I agree with the conclusion of closing it |
This is what I mean: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#timing, https://blog.izs.me/2013/08/designing-apis-for-asynchrony/. You should not have a function that's "sometimes synchronous, sometimes asynchronous" because that makes it hard to orchestrate task order.
Why not async function get(thing) {
const res = await fetch(`${base}/${thing}`);
return await r.json();
} ? Also you might be overthinking about the impact of rewrapping promises—engines are very good at optimizing that away. |
Just cos I was curious. A perf comparison of having the async
if you're downlevelling:
Which illustrates the perf difference. However that difference is slightly worse than I originally mentioned - but it's still on the order of microseconds (one one millionth of a second). You lose 1.2m ops/s by having an unnecessary await - so it's ~1.2 microseconds slower. Unless you're downlevelling to <ES2017 - if you're doing that then the polyfilled async/await is much slower. Downlevelling to <ES2015 is even worse as it also polyfills generators and you get half the perf again. |
Uh oh!
There was an error while loading. Please reload this page.
Before You File a Proposal Please Confirm You Have Done The Following...
My proposal is suitable for this project
Description
The rule would check that we don't cause unnecessary performance losses. The
async
keyword should not be used where TypeScript can already determine that aPromise
is being returned and there is noawait
as addingasync
in that case will only cause a slight performance hitFail Cases
Pass Cases
From https://github.com/sveltejs/svelte/blob/862949d22abf2996594b4315c04fc97e18bc4408/packages/svelte/src/compiler/preprocess/replace_in_code.js#L23:
Additional Info
There is https://typescript-eslint.io/rules/promise-function-async, but it's almost exactly opposite of this. I recently turned on a handful of promise-related rules in
sveltejs/svelte
, but got a ton of pushback on@typescript-eslint/promise-function-async
. The consensus seemed to be that the rule seemed like bad practice. The description of the rule in the docs doesn't seem to match at all what it actually does. I think either its docs could be improved or that rule could be removedThe text was updated successfully, but these errors were encountered: