Index

require-atomic-updates

禁止因使用 awaityield 而导致竞争条件的分配

在编写异步代码时,可能会产生微妙的竞争条件错误。考虑以下示例:

🌐 When writing asynchronous code, it is possible to create subtle race condition bugs. Consider the following example:

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 变量在另一个函数调用中被更新,该更新将会丢失,因为新值在未被读取的情况下被覆盖。

🌐 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.

解决此问题的一种方法是确保在更新 totalLength 的同时读取它,如下所示:

🌐 One way to fix this issue would be to ensure that totalLength is read at the same time as it’s updated, like this:

async function addLengthOfSinglePage(pageNum) {
  const lengthOfThisPage = await getPageLength(pageNum);

  totalLength += lengthOfThisPage;
}

另一种解决方案是完全避免使用可变变量引用:

🌐 Another solution would be to avoid using a mutable variable reference at all:

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);
});

规则详情

🌐 Rule Details

此规则旨在在分配可能基于过时值的情况下报告对变量或属性的分配。

🌐 This rule aims to report assignments to variables or properties in cases where the assignments may be based on outdated values.

变量

🌐 Variables

当此规则在生成器或异步函数中检测到以下执行流程时,它会报告对变量的赋值:

🌐 This rule reports an assignment to a variable when it detects the following execution flow in a generator or async function:

  1. 变量被读取。
  2. 一个 yieldawait 会暂停该功能。
  3. 函数恢复后,从第 1 步开始为变量赋值。

步骤 3 中的赋值被报告是因为它可能被错误地解析,因为步骤 1 中变量的值可能在步骤 2 和步骤 3 之间发生了变化。特别是,如果该变量可以从其他执行上下文访问(例如,如果它不是局部变量,因此其他函数可以更改它),则在函数在步骤 2 暂停期间,变量的值可能已经在其他地方发生变化。

🌐 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.

请注意,在以下任何情况下,该规则都不会报告步骤 3 中的分配:

🌐 Note that the rule does not report the assignment in step 3 in any of the following cases:

  • 如果在第 2 步和第 3 步之间再次读取变量。
  • 如果在函数暂停时无法访问变量(例如,如果它是局部变量)。

此规则的错误代码示例:

🌐 Examples of incorrect code for this rule:

在线运行
/* 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;
}

符合此规则的正确代码示例:

🌐 Examples of correct code for this rule:

在线运行
/* 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;
}

属性

🌐 Properties

当此规则在生成器或异步函数中检测到以下执行流程时,它会通过变量报告对属性的赋值:

🌐 This rule reports an assignment to a property through a variable when it detects the following execution flow in a generator or async function:

  1. 读取变量或对象属性。
  2. 一个 yieldawait 会暂停该功能。
  3. 函数恢复后,将值分配给属性。

这个逻辑类似于变量的逻辑,但更严格,因为步骤3中的属性不必与步骤1中的属性相同。假设流程取决于对象整体的状态。

🌐 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.

此规则的错误代码示例:

🌐 Example of incorrect code for this rule:

在线运行
/* eslint require-atomic-updates: error */

async function foo(obj) {
    if (!obj.done) {
        obj.something = await getSomething();
    }
}

此规则的正确代码示例:

🌐 Example of correct code for this rule:

在线运行
/* eslint require-atomic-updates: error */

async function foo(obj) {
    if (!obj.done) {
        const tmp = await getSomething();
        if (!obj.done) {
            obj.something = tmp;
        }
    }
}

选项

🌐 Options

此规则有一个对象选项:

🌐 This rule has an object option:

  • "allowProperties":当设置为 true 时,该规则不会报告对属性的赋值。默认值是 false

allowProperties

使用 { "allowProperties": true } 选项的此规则的正确代码示例:

🌐 Example of correct code for this rule with the { "allowProperties": true } option:

在线运行
/* eslint require-atomic-updates: ["error", { "allowProperties": true }] */

async function foo(obj) {
    if (!obj.done) {
        obj.something = await getSomething();
    }
}

何时不使用

🌐 When Not To Use It

如果你不使用异步或生成器功能,则无需启用此规则。

🌐 If you don’t use async or generator functions, you don’t need to enable this rule.

版本

此规则是在 ESLint v5.3.0 中引入。

资源