【JavaScript】ES2021の新機能

[ES2023](https://qiita.com/rana_kualu/items/84f66fe970f7feccf367) / [ES2022](https://qiita.com/rana_kualu/items/8bafecd760ae69cfac41) / [ES2021](https://qiita.com/rana_kualu/items/ae3297dd2974fcf047c4)


2016年あたり以降、JavaScriptの新機能は[TC39](https://www.ecma-international.org/technical-committees/tc39/)というところで決められています。

[Proposals](https://github.com/tc39/proposals)を見てみると、箸にも棒にも掛からないものからなんか凄いものまで様々なアイデアが並んでいます。
ここから仕様を洗練していき、ブラウザ上に実装まで行われたものは[Finished Proposals](https://github.com/tc39/proposals/blob/master/finished-proposals.md)となります。

ここでは、2021年版であるES2021に取り入れられたものを見てみることにします。


# ES2021


## [Promise.any](https://github.com/tc39/proposal-promise-any)

複数のPromiseを渡したときに、ひとつでも成功した時点で解決されるPromiseです。

既存の[Promise.all](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)は『全てのPromiseが成功したら』でありPromiseのANDですが、[Promise.any](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/any)はPromiseのORということになります。

```js
const promise1 = xxx;
const promise2 = xxx;
const promise3 = xxx;

Promise.all([promise1, promise2, promise3])
	.then(values => {
		// 全てのPromiseがfulfillされたときに呼ばれる
	})
	.catch(error => {
		// ひとつでもrejectされたら呼ばれる
	});

Promise.any([promise1, promise2, promise3])
	.then(first => {
		// ひとつでもfulfillされたら呼ばれる
	})
	.catch(error => {
		// 全てのPromiseがrejectされたときに呼ばれる
	});
```

複数の宛先にリクエストを投げ、どれかひとつさえ取得できれば残りは不要、みたいなときに使えるでしょう。

anyの実装によって、Promiseの集約メソッドは4種類になります。

```js
const promise1 = xxx;
const promise2 = xxx;
const promise3 = xxx;

Promise.race([promise1, promise2, promise3])
	.then(first => {
		// fulfill/rejectに関わらずひとつでもPromiseが解決したら呼ばれる
	});

Promise.allSettled([promise1, promise2, promise3])
	.then(values => {
		// fulfill/rejectに関わらず全てのPromiseが解決したら呼ばれる
	});
```


最初に返ってきた値を使う[Promise.race](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/race)、全てが返ってくるまで待つ[Promise.allSettled](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled)と、キャッチするタイミングが異なる関数が幾つか存在します。
それぞれ使うのに適切な場面があるので、うまく使い分けるとよいでしょう。


## [String.prototype.replaceAll](https://github.com/tc39/proposal-string-replaceall)

これまでJSには、一致する文字列を全て置換する機能がありませんでした。
[replace](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/replace)は、最初のひとつだけしか置換しません。

```js
'hoge hoge'.replace('hoge', 'fuga'); // "fuga hoge"
```

[StackOverflowの質問](https://stackoverflow.com/questions/1144783/how-to-replace-all-occurrences-of-a-string-in-javascript)には4000超というなかなか見たことのないupvoteがついています。

そんなわけでES2021になってようやく[replaceAll](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll)が登場しました。

```js
'hoge hoge'.replaceAll('hoge', 'fuga'); // "fuga fuga"
```

単純にヒットした文字を全て置換します。

正規表現も使用可能ですが、何故かグローバルフラグ`/g`が必須になります。

```js
'a1212b'.replace(/(\d)(\d)/, '$2$1'); // a2112b 動くが1回だけ
'a1212b'.replace(/(\d)(\d)/g, '$2$1'); // a2121b 動く

'a1212b'.replaceAll(/(\d)(\d)/, '$2$1'); // TypeError: String.prototype.replaceAll called with a non-global RegExp argument
'a1212b'.replaceAll(/(\d)(\d)/g, '$2$1'); // a2121b 動く
```

実はグローバルフラグを付けると通常の`replace`でも全置換になります。
なので、これまでも正規表現を使えば全置換は可能だったのですが、単純な文字列置換にわざわざ正規表現を持ち出したくはないですよね。
実際プロポーザルの動機には「正規表現にすれば全置換できるけどエスケープしたりしないといけないのでつらい」的なことが書いてありました。

`replace`と`replaceAll`で正規表現を使ったときの動作が微妙に異なるのはなんかわかりにくいですが、単純な文字列置換が主目的であり、正規表現はおまけだと考えるとよいかもしれません。
正規表現を扱えるユーザなら`replaceAll`なんかあってもなくてもどうにでもできるでしょうしね。


## [Logical Assignment Operators](https://github.com/tc39/proposal-logical-assignment)

いまいち適切な日本語が見当たらないのですが、`論理演算子+代入演算子`です。

```js
// 論理和代入
a ||= b;
a || (a = b); // ↑と同じ

// 論理積代入
a &&= b;
a && (a = b); // ↑と同じ

// null合体代入
a ??= b;
a ?? (a = b); // ↑と同じ
```

`a ||= b`は、`a`が`true`っぽい値であればそのままで、`false`っぽい値であれば`b`にする、ということになります。
論理演算子`||`と代入演算子`=`の合体ということで`論理代入演算子`とでも呼べばいいですかね。


## [Numeric separators](https://github.com/tc39/proposal-numeric-separator)

数値セパレータです。

```js
 100000000 === 100_000_000; // true
```

数値を`_`で区切って表記できるようになります。
あくまで表記上人間が見やすいようにするだけのもので、実体としては`_`が無い状態と同一です。

10進数以外の数値にも使えます。
また`__`と2個以上連続する、小数点や進数記号の前後につける、などの表記は禁止されます。
何故かRFCではハブられているのですが、[PHPの数値セパレータ](https://qiita.com/rana_kualu/items/d72854f9aec6d7cf8bc6)とほぼ同じ文法です。

```js
42;   // OK
4_2;  // OK
4__2: // SyntaxError: Only one underscore is allowed as numeric separator
42_;  // SyntaxError: Numeric separators are not allowed at the end of numeric literals
_42;  // これはただの変数

0.12;  // OK
0.1_2; // OK
0._12; // SyntaxError: Invalid or unexpected token

0xABCD;  // OK
0xAB_CD; // OK
0x_ABCD; // SyntaxError: Invalid or unexpected token
```

## [WeakRefs](https://github.com/tc39/proposal-weakrefs)

弱い参照です。

[PHPに実装されたとき](https://qiita.com/rana_kualu/items/50f9f5735321fe995ff5#%E5%BC%B1%E3%81%84%E5%8F%82%E7%85%A7)にも思ったのですが、毎回インスタンス使い捨ての言語で弱参照を使う意味がよくわからないんですよね。
Qiitaに[ちょうど詳しい解説があった](https://qiita.com/uhyo/items/5dc97667ba90ce3941cd)ので、詳細はそちらを見てください。
が、やっぱり必要性はよくわからん。
Nodeなら使い道があるのかな？

```js:RFCの例
class FileStream {
  static #cleanUp(heldValue) {
    console.error(`File leaked: ${file}!`);
  }

  static #finalizationGroup = new FinalizationRegistry(FileStream.#cleanUp);

  #file;

  constructor(fileName) {
    this.#file = new File(fileName);
    FileStream.#finalizationGroup.register(this, this.#file, this);
    // 略 ファイルを読み込む
  }

  close() {
    FileStream.#finalizationGroup.unregister(this);
    File.close(this.#file);
  }

  async *[Symbol.iterator]() {
    // 略 this.#fileからデータを読む
  }
}

const fs = new FileStream('path/to/some/file');

for await (const data of fs) {
  // 略 なんかする
}
fs.close();
```

これはRFCに載っていた例です。
・`new FinalizationRegistry`で、GCされたときに呼ばれるコールバック関数を登録する。
・コンストラクタの`register`で、GC対象のオブジェクトを登録する。
・ファイナライザの`unregister`で、登録したオブジェクトを解除する。

`close`する前にGCでオブジェクトが消去されると、`FileStream.#cleanUp`メソッドが呼ばれてコンソールにログが吐かれます。

[ドキュメント](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry#notes_on_cleanup_callbacks)には『重要なロジックには使うな』とか書かれてあってどうすんだこれ。


# 感想

JavaScriptのドラスティックな変化ってのはだいたいES2017-2018あたりでやり尽くされたかんじがあり、それ以降は"ちょっとした便利な機能の追加"ってのが多い気がしますね。
ES2021も、文法が大きく変わるような変更は入っておらず、今後書くのがちょっと楽になるようなものがメインです。

もちろんそれぞれの追加機能も便利ではあるのですが、せっかくだから何かasync/awaitレベルの革新的な機能もほしいところですね。