版本

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 值的总和。如果 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);
});

规则详情

此规则旨在报告对变量或属性的赋值,这些赋值可能基于过时值。

变量

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

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

步骤 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;
}

属性

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

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

此逻辑类似于变量的逻辑,但更严格,因为步骤 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();
    }
}

何时不使用它

如果您不使用异步函数或生成器函数,则无需启用此规则。

版本

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

资源

更改语言