# require-atomic-updates
禁止由于使用 await
或 yield
而可能导致竞争条件的分配
在编写异步代码时,可能会产生微妙的竞争条件错误。考虑以下示例:
let totalLength = 0;
async function addLengthOfSinglePage(pageNum) {
totalLength += await getPageLength(pageNum);
}
Promise.all([addLengthOfSinglePage(1), addLengthOfSinglePage(2)]).then(() => {
console.log('The combined length of both pages is', totalLength);
});
这段代码看起来将调用 getPageLength(1)
和 getPageLength(2)
的结果相加,但实际上 totalLength
的最终值只是两页之一的长度。错误在语句 totalLength += await getPageLength(pageNum);
中。该语句首先读取 totalLength
的初始值,然后调用 getPageLength(pageNum)
并等待该 Promise 完成。最后,它将 totalLength
的值设置为 await getPageLength(pageNum)
和 totalLength
的初始值之和。如果在 getPageLength(pageNum)
Promise 未决期间在单独的函数调用中更新了 totalLength
变量,则该更新将丢失,因为新值被覆盖而未被读取。
解决此问题的一种方法是确保在更新 totalLength
的同时读取它,如下所示:
async function addLengthOfSinglePage(pageNum) {
const lengthOfThisPage = await getPageLength(pageNum);
totalLength += lengthOfThisPage;
}
另一种解决方案是完全避免使用可变变量引用:
Promise.all([getPageLength(1), getPageLength(2)]).then(pageLengths => {
const totalLength = pageLengths.reduce((accumulator, length) => accumulator + length, 0);
console.log('The combined length of both pages is', totalLength);
});
# 规则详情
此规则旨在在分配可能基于过时值的情况下报告对变量或属性的分配。
# 变量
当此规则在生成器或异步函数中检测到以下执行流程时,它会报告对变量的赋值:
报告第 3 步中的赋值是因为它可能被错误地解析,因为第 1 步中的变量值可能在第 2 步和第 3 步之间发生了变化。特别是,如果可以从其他执行上下文访问该变量(例如,如果它不是局部变量,因此其他函数可以更改它),则当函数在步骤 2 中暂停时,该变量的值可能已在其他地方更改.
请注意,在以下任何情况下,该规则都不会报告步骤 3 中的分配:
- 如果在第 2 步和第 3 步之间再次读取变量。
- 如果在函数暂停时无法访问变量(例如,如果它是局部变量)。
此规则的错误代码示例:
/* eslint require-atomic-updates: error */
let result;
async function foo() {
result += await something;
}
async function bar() {
result = result + await something;
}
async function baz() {
result = result + doSomething(await somethingElse);
}
async function qux() {
if (!result) {
result = await initialize();
}
}
function* generator() {
result += yield;
}
此规则的正确代码示例:
/* eslint require-atomic-updates: error */
let result;
async function foobar() {
result = await something + result;
}
async function baz() {
const tmp = doSomething(await somethingElse);
result += tmp;
}
async function qux() {
if (!result) {
const tmp = await initialize();
if (!result) {
result = tmp;
}
}
}
async function quux() {
let localVariable = 0;
localVariable += await something;
}
function* generator() {
result = (yield) + result;
}
# 属性
当此规则在生成器或异步函数中检测到以下执行流程时,它会通过变量报告对属性的赋值:
此逻辑类似于变量的逻辑,但更严格,因为步骤 3 中的属性不必与步骤 1 中的属性相同。假设流取决于整个对象的状态。
此规则的错误代码示例:
/* eslint require-atomic-updates: error */
async function foo(obj) {
if (!obj.done) {
obj.something = await getSomething();
}
}
此规则的正确代码示例:
/* eslint require-atomic-updates: error */
async function foo(obj) {
if (!obj.done) {
const tmp = await getSomething();
if (!obj.done) {
obj.something = tmp;
}
}
}
# 选项
此规则有一个对象选项:
"allowProperties"
:当设置为true
时,该规则不报告对属性的分配。默认为false
。
# allowProperties
此规则使用 { "allowProperties": true }
选项的正确代码示例:
/* eslint require-atomic-updates: ["error", { "allowProperties": true }] */
async function foo(obj) {
if (!obj.done) {
obj.something = await getSomething();
}
}
# 何时不使用
如果您不使用异步或生成器功能,则无需启用此规则。