元ネタ: 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
がめちゃくちゃになってしまって、意図しない結果が生成される可能性があります。