]> git.proxmox.com Git - pve-eslint.git/blob - eslint/docs/rules/require-atomic-updates.md
import eslint 7.28.0
[pve-eslint.git] / eslint / docs / rules / require-atomic-updates.md
1 # Disallow assignments that can lead to race conditions due to usage of `await` or `yield` (require-atomic-updates)
2
3 When writing asynchronous code, it is possible to create subtle race condition bugs. Consider the following example:
4
5 ```js
6 let totalLength = 0;
7
8 async function addLengthOfSinglePage(pageNum) {
9 totalLength += await getPageLength(pageNum);
10 }
11
12 Promise.all([addLengthOfSinglePage(1), addLengthOfSinglePage(2)]).then(() => {
13 console.log('The combined length of both pages is', totalLength);
14 });
15 ```
16
17 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.
18
19 One 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
22 async function addLengthOfSinglePage(pageNum) {
23 const lengthOfThisPage = await getPageLength(pageNum);
24
25 totalLength += lengthOfThisPage;
26 }
27 ```
28
29 Another solution would be to avoid using a mutable variable reference at all:
30
31 ```js
32 Promise.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
41 This rule aims to report assignments to variables or properties where all of the following are true:
42
43 * A variable or property is reassigned to a new value which is based on its old value.
44 * A `yield` or `await` expression interrupts the assignment after the old value is read, and before the new value is set.
45 * The rule cannot easily verify that the assignment is safe (e.g. if an assigned variable is local and would not be readable from anywhere else while the function is paused).
46
47 Examples of **incorrect** code for this rule:
48
49 ```js
50 /* eslint require-atomic-updates: error */
51
52 let result;
53 async function foo() {
54 result += await somethingElse;
55
56 result = result + await somethingElse;
57
58 result = result + doSomething(await somethingElse);
59 }
60
61 function* bar() {
62 result += yield;
63
64 result = result + (yield somethingElse);
65
66 result = result + doSomething(yield somethingElse);
67 }
68 ```
69
70 Examples of **correct** code for this rule:
71
72 ```js
73 /* eslint require-atomic-updates: error */
74
75 let result;
76 async function foo() {
77 result = await somethingElse + result;
78
79 let tmp = await somethingElse;
80 result += tmp;
81
82 let localVariable = 0;
83 localVariable += await somethingElse;
84 }
85
86 function* bar() {
87 result = (yield) + result;
88
89 result = (yield somethingElse) + result;
90
91 result = doSomething(yield somethingElse, result);
92 }
93 ```
94
95 ## When Not To Use It
96
97 If you don't use async or generator functions, you don't need to enable this rule.