◆ Promise.any
◆ WeakRef
◆ &&=, ||=, ??=
◆ 1_000_000

しばらく見てなかったので JavaScript の新機能のステータスを見てみました
https://github.com/tc39/proposals/blob/master/finished-proposals.md

以前見たときには ES2021 予定は replaceAll だけだったのに

  • Promise.any
  • WeakRefs
  • Logical Assignment Operators
  • Numeric separators

が増えています

どれもブラウザで動かせたので使ってみました

String.prototype.replaceAll

これは Chrome 85 から使えます
今 84 なので次のリリース (今月末) です
beta 版なら今でも使えます

これまで文字列を置換しようとすると replace メソッドを使っていました
検索対象は文字列型と正規表現型の両方を受け取れましたが 文字列型の場合は最初の 1 つしか置換してくれません
全部を置換したい場合は正規表現型にして g オプションが必須です

"foo".replace("o", "a")
// "fao"
"foo".replace(/o/, "a")
// "fao"
"foo".replace(/o/g, "a")
// "faa"

検索文字が固定で リテラルで書くのならともかく ユーザー入力値だと正規表現型に変換するのはひと手間必要ですし 正規表現で特殊な意味を持つ使う文字列が含まれることもあるため エスケープが必要です
それなのに JavaScript の標準機能では正規表現用のエスケープをする関数が用意されていません
その不便さを解消してくれるのが replaceAll です
文字列型の指定で見つかった全部を置換してくれます

"foo".replaceAll("o", "a")
// "faa"

Promise.any

これも Chrome 85 からです

初期の Promise のメソッドは resolve/reject を除くと all と race がありました
これらは複数の Promise を組み合わせて 1 つの Promise にしてくれるものです
この系統で ES2020 で allSettled が追加され ES2021 では any が追加になります

any は all と対応するもので all がすべての Promise が成功したら成功になるのに対し any はどれかの Promise が成功したら成功になります
全部失敗にならなければ途中で失敗があっても無視されます
race というのもありますが こっちだと成功失敗とわずに一番最初に状態が変わった Promise の結果になります
10 個中 9 個が失敗しても 最後の 1 つが成功なら成功としてその結果がほしいというときには使えません
そういうときに使うために新しくできたものが any です

まとめると

all:
resolved:
全部が resolved になったとき
rejected:
どれかが rejected になったとき
any:
resolved:
どれかが resolved になったとき
rejected:
全部が rejected になったとき
race:
resolved:
どれかが resolved になったとき
rejected:
どれかが rejected になったとき
allSettled:
resolved:
全部が resolved か rejected になったとき
rejected:
ならない

WeakRefs

これは Chrome 84 で使えます

すでに WeakMap や WeakSet がありそれに近い Weak 系ですがこれらとはちょっと違うものです
WeakRef のコンストラクタに弱参照化したいオブジェクトを渡します
WeakMap などのようにオブジェクトである必要があって 1 とか入れるとエラーになります

弱参照化したオブジェクトは WeakRef インスタンスの deref メソッドを呼び出すと取得できます
ただし 弱参照化されたオブジェクトは この WeakRef 外に参照がなければ GC の対象になります
GC されると deref で取得できなくなり undefined が返ってきます

消えるのは GC のタイミング次第なので devtools を使ってると消えなかったりで確認がしづらいものです
こういうページを用意しました

<!doctype html>

<div id="t"></div>

<script>
const log = (...args) => {
t.innerText += args.map(a => JSON.stringify(a)).join(" ") + "\n"
}

const obj = {
value: { a: 1 }
}

const ref = new WeakRef(obj.value)

log(ref.deref())

obj.value = null

log(ref.deref())

let n = 0
setInterval(() => {
log(++n, ref.deref())
}, 1000)
</script>

devtools がなくても確認できるように画面に追記します
obj.value が弱参照化するもので 一度 deref 結果を表示したら null で上書きし GC 対象にします
その後すぐと毎秒ごとに deref 結果を追記します

結果はこうなりました

{"a":1}
{"a":1}
1 {"a":1}
2 {"a":1}
3 {"a":1}
4 {"a":1}
5 {"a":1}
6 {"a":1}
7 {"a":1}
8 {"a":1}
9
10
11
12

8 秒後までは残っていて 9 秒後には GC されていました
これは Chrome 84 での結果ですが GC 次第なので ページの重さとかブラウザの種類とかでタイミングは変わります

これのおかげでどこかに参照が残っていてメモリリークになってないかのチェックがやりやすくなりますね
これまででも WeakSet に入れておいて devtools で見るとか工夫すれば一応できましたけどあんまりやりやすいものでもなく 裏技っぽい方法でしたし

Logical Assignment Operators

これは Chrome 85 からです

JavaScript の二項演算子 +, *, & などには +=, *=, &= と言った = 付きのものがあります
しかし && と || にはなぜかありませんでした
<< や >> や ** にはあるので 2 文字だからパーサの問題でというわけでもないと思います

入れない理由もないからか && と || にも = 付きの &&= と ||= が追加され使えるようになりました
最近追加された ?? 演算子も || と同じような扱いだったので = 付きがありませんでしたが ??= も追加されました

let a = true
a &&= false
// false
a
// false
a ||= true
// true
a
// true
a = null
a ??= 1
// 1
a
// 1
a ??= 2
// 1
a
// 1

Numeric separators

これは以前から使えたものです

数値のリテラルに _ を挟んで読みやすくかけます

123 === 1_2_3
// true
1_000_000_000.000_001
// 1000000000.000001

完全に自由に _ を入れられるわけでもなく _ を 2 連続で書けなかったり最初と最後に書けないなどの制限はあります
長くなると適度な区切りで __ を使ったりしたいんですけどね
他言語だと Rust は連続ありですが Python はダメでした
この辺は言語次第ですね