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. 变量被读取。

    ¥The variable is read.

  2. yieldawait 暂停该功能。

    ¥A yield or await pauses the function.

  3. 函数恢复后,从第 1 步开始为变量赋值。

    ¥After the function is resumed, a value is assigned to the variable from step 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 步之间再次读取变量。

    ¥If the variable is read again between steps 2 and 3.

  • 如果在函数暂停时无法访问变量(例如,如果它是局部变量)。

    ¥If the variable cannot be accessed while the function is paused (for example, if it’s a local variable).

此规则的错误代码示例:

¥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. 读取变量或对象属性。

    ¥The variable or object property is read.

  2. yieldawait 暂停该功能。

    ¥A yield or await pauses the function.

  3. 函数恢复后,将值分配给属性。

    ¥After the function is resumed, a value is assigned to a property.

此逻辑类似于变量的逻辑,但更严格,因为步骤 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": When set to true, the rule does not report assignments to properties. Default is 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 中引入。

资源

ESLint 中文网
粤ICP备13048890号