]> git.proxmox.com Git - pve-eslint.git/blame - eslint/docs/rules/require-atomic-updates.md
import 8.3.0 source
[pve-eslint.git] / eslint / docs / rules / require-atomic-updates.md
CommitLineData
eb39fafa
DC
1# Disallow assignments that can lead to race conditions due to usage of `await` or `yield` (require-atomic-updates)
2
3When writing asynchronous code, it is possible to create subtle race condition bugs. Consider the following example:
4
5```js
6let totalLength = 0;
7
8async function addLengthOfSinglePage(pageNum) {
9 totalLength += await getPageLength(pageNum);
10}
11
12Promise.all([addLengthOfSinglePage(1), addLengthOfSinglePage(2)]).then(() => {
13 console.log('The combined length of both pages is', totalLength);
14});
15```
16
17This code looks like it will sum the results of calling `getPageLength(1)` and `getPageLength(2)`, but in reality the final value of `totalLength` will only be the length of one of the two pages. The bug is in the statement `totalLength += await getPageLength(pageNum);`. This statement first reads an initial value of `totalLength`, then calls `getPageLength(pageNum)` and waits for that Promise to fulfill. Finally, it sets the value of `totalLength` to the sum of `await getPageLength(pageNum)` and the *initial* value of `totalLength`. If the `totalLength` variable is updated in a separate function call during the time that the `getPageLength(pageNum)` Promise is pending, that update will be lost because the new value is overwritten without being read.
18
19One way to fix this issue would be to ensure that `totalLength` is read at the same time as it's updated, like this:
20
21```js
22async function addLengthOfSinglePage(pageNum) {
23 const lengthOfThisPage = await getPageLength(pageNum);
24
25 totalLength += lengthOfThisPage;
26}
27```
28
29Another solution would be to avoid using a mutable variable reference at all:
30
31```js
32Promise.all([getPageLength(1), getPageLength(2)]).then(pageLengths => {
33 const totalLength = pageLengths.reduce((accumulator, length) => accumulator + length, 0);
34
35 console.log('The combined length of both pages is', totalLength);
36});
37```
38
39## Rule Details
40
609c276f 41This rule aims to report assignments to variables or properties in cases where the assignments may be based on outdated values.
eb39fafa 42
609c276f
TL
43### Variables
44
45This rule reports an assignment to a variable when it detects the following execution flow in a generator or async function:
46
471. The variable is read.
482. A `yield` or `await` pauses the function.
493. After the function is resumed, a value is assigned to the variable from step 1.
50
51The assignment in step 3 is reported because it may be incorrectly resolved because the value of the variable from step 1 may have changed between steps 2 and 3. In particular, if the variable can be accessed from other execution contexts (for example, if it is not a local variable and therefore other functions can change it), the value of the variable may have changed elsewhere while the function was paused in step 2.
52
53Note that the rule does not report the assignment in step 3 in any of the following cases:
54
55* If the variable is read again between steps 2 and 3.
56* If the variable cannot be accessed while the function is paused (for example, if it's a local variable).
eb39fafa
DC
57
58Examples of **incorrect** code for this rule:
59
60```js
61/* eslint require-atomic-updates: error */
62
63let result;
eb39fafa 64
609c276f
TL
65async function foo() {
66 result += await something;
67}
eb39fafa 68
609c276f
TL
69async function bar() {
70 result = result + await something;
eb39fafa
DC
71}
72
609c276f
TL
73async function baz() {
74 result = result + doSomething(await somethingElse);
75}
eb39fafa 76
609c276f
TL
77async function qux() {
78 if (!result) {
79 result = await initialize();
80 }
81}
eb39fafa 82
609c276f
TL
83function* generator() {
84 result += yield;
eb39fafa
DC
85}
86```
87
88Examples of **correct** code for this rule:
89
90```js
91/* eslint require-atomic-updates: error */
92
93let result;
eb39fafa 94
609c276f
TL
95async function foobar() {
96 result = await something + result;
97}
98
99async function baz() {
100 const tmp = doSomething(await somethingElse);
101 result += tmp;
102}
103
104async function qux() {
105 if (!result) {
106 const tmp = await initialize();
107 if (!result) {
108 result = tmp;
109 }
110 }
111}
112
113async function quux() {
114 let localVariable = 0;
115 localVariable += await something;
116}
117
118function* generator() {
119 result = (yield) + result;
120}
121```
122
123### Properties
124
125This rule reports an assignment to a property through a variable when it detects the following execution flow in a generator or async function:
126
1271. The variable or object property is read.
1282. A `yield` or `await` pauses the function.
1293. After the function is resumed, a value is assigned to a property.
eb39fafa 130
609c276f
TL
131This logic is similar to the logic for variables, but stricter because the property in step 3 doesn't have to be the same as the property in step 1. It is assumed that the flow depends on the state of the object as a whole.
132
133Example of **incorrect** code for this rule:
134
135```js
136/* eslint require-atomic-updates: error */
137
138async function foo(obj) {
139 if (!obj.done) {
140 obj.something = await getSomething();
141 }
eb39fafa 142}
609c276f 143```
eb39fafa 144
609c276f 145Example of **correct** code for this rule:
eb39fafa 146
609c276f
TL
147```js
148/* eslint require-atomic-updates: error */
149
150async function foo(obj) {
151 if (!obj.done) {
152 const tmp = await getSomething();
153 if (!obj.done) {
154 obj.something = tmp;
155 }
156 }
157}
158```
159
160## Options
161
162This rule has an object option:
163
164* `"allowProperties"`: When set to `true`, the rule does not report assignments to properties. Default is `false`.
165
166### allowProperties
167
168Example of **correct** code for this rule with the `{ "allowProperties": true }` option:
169
170```js
171/* eslint require-atomic-updates: ["error", { "allowProperties": true }] */
eb39fafa 172
609c276f
TL
173async function foo(obj) {
174 if (!obj.done) {
175 obj.something = await getSomething();
176 }
eb39fafa
DC
177}
178```
179
180## When Not To Use It
181
182If you don't use async or generator functions, you don't need to enable this rule.