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);
});
Подробное описание правила
Это правило предназначено для выявления присваиваний переменным или свойствам в тех случаях, когда присваивания могут основываться на устаревших значениях.
Переменные
Это правило сообщает об присваивании переменной, когда оно обнаруживает следующий поток выполнения в функции-генераторе или асинхронной функции:
- Переменная считывается.
- Пауза функции из-за
yieldилиawait. - После возобновления функции переменной из шага 1 присваивается значение.
Присваивание в шаге 3 сообщается, потому что оно может быть неправильно решено, поскольку значение переменной из шага 1 может измениться между шагами 2 и 3. В частности, если к переменной можно получить доступ из других контекстов выполнения (например, если это не локальная переменная, и, следовательно, другие функции могут ее изменить), значение переменной может измениться в другом месте, пока функция приостанавливалась в шаге 2.
Обратите внимание, что правило не сообщает об присваивании в шаге 3 ни в одном из следующих случаев:
- Если переменная снова читается между шагами 2 и 3.
- Если к переменной нельзя получить доступ во время приостановки функции (например, если это локальная переменная).
Примеры некорректного кода для этого правила:
/* eslint require-atomic-updates: error */
let result;
async function foo() {
;
}
async function bar() {
;
}
async function baz() {
;
}
async function qux() {
if (!result) {
;
}
}
function* generator() {
;
}
Примеры корректного кода для этого правила:
/* 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;
}
Свойства
Это правило сообщает об присваивании свойству через переменную, когда оно обнаруживает следующий поток выполнения в функции-генераторе или асинхронной функции:
- Переменная или свойство объекта считывается.
- Пауза функции из-за
yieldилиawait. - После возобновления функции свойству присваивается значение.
Эта логика аналогична логике для переменных, но строже, потому что свойство в шаге 3 не обязательно должно быть таким же, как свойство в шаге 1. Предполагается, что поток зависит от состояния объекта в целом.
Пример некорректного кода для этого правила:
/* eslint require-atomic-updates: error */
async function foo(obj) {
if (!obj.done) {
;
}
}
Пример корректного кода для этого правила:
/* 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.
Ресурсы
© OpenJS Foundation and other contributors
Licensed under the MIT License.
https://eslint.org/docs/latest/rules/require-atomic-updates