版本

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 中引入。

資源

變更語言