]> git.proxmox.com Git - pve-eslint.git/blob - eslint/docs/src/rules/require-atomic-updates.md
import 8.23.1 source
[pve-eslint.git] / eslint / docs / src / rules / require-atomic-updates.md
1 ---
2 title: require-atomic-updates
3 layout: doc
4 rule_type: problem
5 ---
6
7
8 When writing asynchronous code, it is possible to create subtle race condition bugs. Consider the following example:
9
10 ```js
11 let totalLength = 0;
12
13 async function addLengthOfSinglePage(pageNum) {
14 totalLength += await getPageLength(pageNum);
15 }
16
17 Promise.all([addLengthOfSinglePage(1), addLengthOfSinglePage(2)]).then(() => {
18 console.log('The combined length of both pages is', totalLength);
19 });
20 ```
21
22 This 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.
23
24 One way to fix this issue would be to ensure that `totalLength` is read at the same time as it's updated, like this:
25
26 ```js
27 async function addLengthOfSinglePage(pageNum) {
28 const lengthOfThisPage = await getPageLength(pageNum);
29
30 totalLength += lengthOfThisPage;
31 }
32 ```
33
34 Another solution would be to avoid using a mutable variable reference at all:
35
36 ```js
37 Promise.all([getPageLength(1), getPageLength(2)]).then(pageLengths => {
38 const totalLength = pageLengths.reduce((accumulator, length) => accumulator + length, 0);
39
40 console.log('The combined length of both pages is', totalLength);
41 });
42 ```
43
44 ## Rule Details
45
46 This rule aims to report assignments to variables or properties in cases where the assignments may be based on outdated values.
47
48 ### Variables
49
50 This rule reports an assignment to a variable when it detects the following execution flow in a generator or async function:
51
52 1. The variable is read.
53 2. A `yield` or `await` pauses the function.
54 3. After the function is resumed, a value is assigned to the variable from step 1.
55
56 The 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.
57
58 Note that the rule does not report the assignment in step 3 in any of the following cases:
59
60 * If the variable is read again between steps 2 and 3.
61 * If the variable cannot be accessed while the function is paused (for example, if it's a local variable).
62
63 Examples of **incorrect** code for this rule:
64
65 ::: incorrect
66
67 ```js
68 /* eslint require-atomic-updates: error */
69
70 let result;
71
72 async function foo() {
73 result += await something;
74 }
75
76 async function bar() {
77 result = result + await something;
78 }
79
80 async function baz() {
81 result = result + doSomething(await somethingElse);
82 }
83
84 async function qux() {
85 if (!result) {
86 result = await initialize();
87 }
88 }
89
90 function* generator() {
91 result += yield;
92 }
93 ```
94
95 :::
96
97 Examples of **correct** code for this rule:
98
99 ::: correct
100
101 ```js
102 /* eslint require-atomic-updates: error */
103
104 let result;
105
106 async function foobar() {
107 result = await something + result;
108 }
109
110 async function baz() {
111 const tmp = doSomething(await somethingElse);
112 result += tmp;
113 }
114
115 async function qux() {
116 if (!result) {
117 const tmp = await initialize();
118 if (!result) {
119 result = tmp;
120 }
121 }
122 }
123
124 async function quux() {
125 let localVariable = 0;
126 localVariable += await something;
127 }
128
129 function* generator() {
130 result = (yield) + result;
131 }
132 ```
133
134 :::
135
136 ### Properties
137
138 This rule reports an assignment to a property through a variable when it detects the following execution flow in a generator or async function:
139
140 1. The variable or object property is read.
141 2. A `yield` or `await` pauses the function.
142 3. After the function is resumed, a value is assigned to a property.
143
144 This 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.
145
146 Example of **incorrect** code for this rule:
147
148 ::: incorrect
149
150 ```js
151 /* eslint require-atomic-updates: error */
152
153 async function foo(obj) {
154 if (!obj.done) {
155 obj.something = await getSomething();
156 }
157 }
158 ```
159
160 :::
161
162 Example of **correct** code for this rule:
163
164 ::: correct
165
166 ```js
167 /* eslint require-atomic-updates: error */
168
169 async function foo(obj) {
170 if (!obj.done) {
171 const tmp = await getSomething();
172 if (!obj.done) {
173 obj.something = tmp;
174 }
175 }
176 }
177 ```
178
179 :::
180
181 ## Options
182
183 This rule has an object option:
184
185 * `"allowProperties"`: When set to `true`, the rule does not report assignments to properties. Default is `false`.
186
187 ### allowProperties
188
189 Example of **correct** code for this rule with the `{ "allowProperties": true }` option:
190
191 ::: correct
192
193 ```js
194 /* eslint require-atomic-updates: ["error", { "allowProperties": true }] */
195
196 async function foo(obj) {
197 if (!obj.done) {
198 obj.something = await getSomething();
199 }
200 }
201 ```
202
203 :::
204
205 ## When Not To Use It
206
207 If you don't use async or generator functions, you don't need to enable this rule.