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
值的总和。如果 totalLength
变量在 getPageLength(pageNum)
Promise 处于挂起状态期间在单独的函数调用中更新,则该更新将丢失,因为新值在未读取的情况下被覆盖了。
解决此问题的一种方法是确保在更新 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);
});
规则详情
此规则旨在报告对变量或属性的赋值,这些赋值可能基于过时值。
变量
此规则在检测到生成器或异步函数中的以下执行流程时,会报告对变量的赋值
- 读取变量。
yield
或await
使函数暂停。- 函数恢复后,将值赋值给步骤 1 中的变量。
步骤 3 中的赋值将被报告,因为它可能被错误地解析,因为步骤 1 中变量的值可能在步骤 2 和 3 之间发生了变化。特别是,如果变量可以从其他执行上下文访问(例如,如果不是局部变量,因此其他函数可以更改它),则变量的值可能在函数在步骤 2 中暂停时在其他地方发生了变化。
请注意,该规则不会在以下任何情况下报告步骤 3 中的赋值
- 如果在步骤 2 和 3 之间再次读取了变量。
- 如果在函数暂停时无法访问变量(例如,如果它是局部变量)。
此规则的错误代码示例
/* eslint require-atomic-updates: error */
let result;
async function foo() {
;
}
async function bar() {
;
}
async function baz() {
;
}
async function qux() {
if (!result) {
;
}
}
function* generator() {
;
}
此规则的正确代码示例
/* 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;
}
属性
此规则在检测到生成器或异步函数中的以下执行流程时,会报告通过变量对属性的赋值
- 读取变量或对象属性。
yield
或await
使函数暂停。- 函数恢复后,将值赋值给属性。
此逻辑类似于变量的逻辑,但更严格,因为步骤 3 中的属性不必与步骤 1 中的属性相同。假设该流程依赖于对象的整体状态。
此规则的错误代码示例
/* eslint require-atomic-updates: error */
async function foo(obj) {
if (!obj.done) {
;
}
}
此规则的正确代码示例
/* 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();
}
}
何时不使用它
如果您不使用异步函数或生成器函数,则无需启用此规则。
版本
此规则是在 ESLint v5.3.0 中引入的。