元ネタ: eslint/eslint#10405
このルール提案は「アトミックではない変数の更新を警告したい」というものです。
アトミックではない変数の更新というのは、マルチスレッド プログラミングの最初の方に出てくるアレです。または、RDB のトランザクションの説明で出てくるアレです。
JavaScript のコードはシングルスレッドで動くのに、アトミックではない変数の更新なんてあるの? と思ったらありました。スレッドを自由に作成できる言語と違って、どこでも自由に別処理が割り込んでくるわけではありません。しかし、yieldやawaitによって、式の途中で別処理が割り込んでくることがありえるのです。
See the Pen Threading problem by Toru Nagashima (@mysticatea) on CodePen.
肝はここです。
total += await getFileSize(file)
次のように動作します。
- 変数
totalの値をメモリに読み込む。 - 処理
await getFileSize(file)を実行して、結果をメモリに読み込む。 - 手順 1 と手順 2 の値を足し合わせた結果で変数
totalの値を書き換える。
手順 2 がawait式を持っているため、ここに別処理が割り込むことができます。そして、その別処理の中で変数totalの値が変更された場合に意図しない結果になるのです。
この例では、reduce関数を使うなどして共有変数を持たないようにすべきですね。
function getTotalFileSize(files) {
return Promise.all(files.map(getFileSize)).reduce((a, b) => a + b, 0)
}
別の例です。
const pattern = /\d{3}/g
// 文字列`test`の中にある特定パターンを列挙する関数。
function* iteratePattern(text) {
let match = null
pattern.lastIndex = 0
while ((match = pattern.exec(text)) != null) {
yield match
}
}
この関数はyield式を持つため、yield matchの部分に別処理が割り込むことができます。すると、pattern.lastIndexがめちゃくちゃになってしまって、意図しない結果が生成される可能性があります。