Spec-Zone.ru › ESLint

require-atomic-updates

Запретить присваивания, которые могут привести к гонкам из-за использования await или yield

При написании асинхронного кода можно создать тонкие ошибки гонки. Рассмотрим следующий пример:

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 обновляется в отдельном вызове функции во время ожидания выполнения Promise getPageLength(pageNum), это обновление будет утеряно, так как новое значение перезапишется без чтения.

Один из способов исправить эту проблему — убедиться, что 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. Пауза функции из-за yield или await.
  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. Пауза функции из-за yield или await.
  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();
    }
}

Когда не следует использовать

Если вы не используете асинхронные или функции-генераторы, вам не нужно включать это правило.

Версия

Это правило было введено в ESLint v5.3.0.

Ресурсы

  • Исходный код правила
  • Исходный код тестов

© OpenJS Foundation and other contributors
Licensed under the MIT License.
https://eslint.org/docs/latest/rules/require-atomic-updates

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API