-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
Rule proposal: no-await-with-operators #10405
Comments
I'm not sure this warrants a rule; in the linked tweet, this only is "confusing" because the variable is being mutated both inside and outside the async function. |
Well, this is quite simple example. The file size reading example in the video is way more confusing since the variable is syntax wise mutated only in one place. const wait = t => new Promise(r => setTimeout(r, t));
const files = ["file1", "file12", "file123", "file1234", "file12345"];
async function getFileSize(file) {
console.log("reading", file);
await wait(Math.round(Math.random() * 100)); // simulate disk io slowness
return file.length;
}
let total = 0;
async function sumAll() {
await Promise.all(
files.map(async file => {
total += await getFileSize(file);
}),
);
console.log(total); // quite random output
}
sumAll(); |
To me, it seems like the confusing part of that code is the use of operator assignment with an x += await foo; If I'm understanding correctly, if two functions run that code at the same time, Maybe a rule could report an error whenever (a) a statement assigns to a variable, (b) the assigned expression contains an |
Interesting. In my understanding, // this is same as `x += await y`
x = x + await y
So..., this is same as a popular problem in multi-threading software. Or we have often seen it in the explanation of the RDB transactions. Clearer expression of what happens is: let local1 = x
let local2 = await y
x = local1 + local2 I think that we can consider the following rule, " var outer = 0
var outerObj = {}
var outerFunc = () => {}
async function f(param) {
//× BAD
outer += await x
outer = outer + await x
outerObj.a = outerObj.a + await x
outerObj.a = outerFunc() + await x
param.a = param.a + await x
param.a = outerObj.a + await x
param.a = outerFunc() + await x
outer = g(outer, await x)
//× BAD maybe; I'm not sure if we should warn this case.
const local1 = outer
outer = local1 + await x
//✔ GOOD; this is atomic
outer = (await x) + outer
outer = g(await x, outer)
} |
This one may not be a valid example: outer = g(await x, outer) What if |
I don't think this really needs a rule specific to await - the simple best-practices answer in JavaScript is don't mutate or reassign things, and if you do, be very very careful about it. |
I agree that using immutable objects/bindings is often a good idea, but sometimes developers still need to use mutable objects/bindings for a variety of reasons. I think having a rule to make that use case safer is better than not having such a rule. |
That's correct, but it doesn't convince me that this should be invalid example. It seems like this rule is generally intended to avoid "race condition" situations where an variable is updated based on its original value, and the updates don't see each others' results. Since |
It seems to me that this should be a new option of operator-assignment . I agree with others that this is sufficiently confusing and would make a good candidate for a rule. |
I can give a motivating example with totally reasonable JavaScript: async function countFiles(dir) {
let files = await readdir(dir);
let count = 0;
await Promise.all(files.map(file => {
let filePath = path.join(dir, file);
let fileStat = await stat(filePath);
if (fileStat.isDirectory()) {
count += await countFiles(file);
} else {
count += 1;
}
});
return count;
} I doubt that most (even experienced) developers would be able to spot the bug here. This just seems like a footgun and that there's little benefit in allowing |
I'll champion this proposal based on the behavior proposed in #10405 (comment). |
now it is accepted. |
This adds a `no-atomic-updates` rule, designed to prevent possible race conditions as described in #10405. Specifically, the rule activates whenever (a) an assignment expression assigns to a new value that depends on its old value, and (b) the execution is interrupted with `await` or `yield` between reading the old value and assigning the new value. As an exception, the rule does not report an error if the assignment expression is assigning to a local variable, provided that the variable is never leaked outside of the surrounding function via a closure.
This adds a `require-atomic-updates` rule, designed to prevent possible race conditions as described in #10405. Specifically, the rule activates whenever (a) an assignment expression assigns to a new value that depends on its old value, and (b) the execution is interrupted with `await` or `yield` between reading the old value and assigning the new value. As an exception, the rule does not report an error if the assignment expression is assigning to a local variable, provided that the variable is never leaked outside of the surrounding function via a closure.
What category of rule is this? (place an "X" next to just one item)
[ ] Enforces code style
[x] Warns about a potential error
[ ] Suggests an alternate way of doing something
[ ] Other (please specify:)
Not sure about the rule name but I think this would be a good thing to avoid in any circumstances as it is very surprising.
Jake Archibald explains it very well here https://twitter.com/jaffathecake/status/999610181452533760
So to sum it up this should warn:
and this
but this should be ok
Why should this rule be included in ESLint (instead of a plugin)?
It's not related to any library and can be a source for really confusing bugs. This is not a very well known Javascript feature and I think it should be considered to be a "bad part" which should be avoided.
The text was updated successfully, but these errors were encountered: