版本

require-atomic-updates

禁止由于使用 awaityield 而可能导致竞争条件的赋值

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

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

规则详情

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

变量

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

  1. 读取变量。
  2. yieldawait 暂停函数。
  3. 在函数恢复后,从步骤 1 中的变量赋值。

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

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

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

此规则的错误代码示例

在 Playground 中打开
/* 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;
}

此规则的正确代码示例

在 Playground 中打开
/* 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;
}

属性

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

  1. 读取变量或对象属性。
  2. yieldawait 暂停函数。
  3. 在函数恢复后,将值赋给属性。

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

此规则的错误代码示例

在 Playground 中打开
/* eslint require-atomic-updates: error */

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

此规则的正确代码示例

在 Playground 中打开
/* 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 } 选项时,此规则的正确代码示例

在 Playground 中打开
/* eslint require-atomic-updates: ["error", { "allowProperties": true }] */

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

何时不使用它

如果您不使用 async 或 generator 函数,则无需启用此规则。

版本

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

资源

更改语言