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