2016年あたり以降、JavaScriptの新機能はTC39というところで決められています。
Proposalsを見てみると、箸にも棒にも掛からないものからなんか凄いものまで様々なアイデアが並んでいます。
ここから仕様を洗練していき、ブラウザ上に実装まで行われたものはFinished Proposalsとなります。
ここでは、2021年版であるES2021に取り入れられたものを見てみることにします。
ES2021
Promise.any
複数のPromiseを渡したときに、ひとつでも成功した時点で解決されるPromiseです。
既存のPromise.allは『全てのPromiseが成功したら』でありPromiseのANDですが、Promise.anyはPromiseのORということになります。
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種類になります。
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、全てが返ってくるまで待つPromise.allSettledと、キャッチするタイミングが異なる関数が幾つか存在します。
それぞれ使うのに適切な場面があるので、うまく使い分けるとよいでしょう。
String.prototype.replaceAll
これまでJSには、一致する文字列を全て置換する機能がありませんでした。
replaceは、最初のひとつだけしか置換しません。
'hoge hoge'.replace('hoge', 'fuga'); // "fuga hoge"
StackOverflowの質問には4000超というなかなか見たことのないupvoteがついています。
そんなわけでES2021になってようやくreplaceAllが登場しました。
'hoge hoge'.replaceAll('hoge', 'fuga'); // "fuga fuga"
単純にヒットした文字を全て置換します。
正規表現も使用可能ですが、何故かグローバルフラグ/g
が必須になります。
'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
いまいち適切な日本語が見当たらないのですが、論理演算子+代入演算子
です。
// 論理和代入
a ||= b;
a || (a = b); // ↑と同じ
// 論理積代入
a &&= b;
a && (a = b); // ↑と同じ
// null合体代入
a ??= b;
a ?? (a = b); // ↑と同じ
a ||= b
は、a
がtrue
っぽい値であればそのままで、false
っぽい値であればb
にする、ということになります。
論理演算子||
と代入演算子=
の合体ということで論理代入演算子
とでも呼べばいいですかね。
Numeric separators
数値セパレータです。
100000000 === 100_000_000; // true
数値を_
で区切って表記できるようになります。
あくまで表記上人間が見やすいようにするだけのもので、実体としては_
が無い状態と同一です。
10進数以外の数値にも使えます。
また__
と2個以上連続する、小数点や進数記号の前後につける、などの表記は禁止されます。
何故かRFCではハブられているのですが、PHPの数値セパレータとほぼ同じ文法です。
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
弱い参照です。
PHPに実装されたときにも思ったのですが、毎回インスタンス使い捨ての言語で弱参照を使う意味がよくわからないんですよね。
Qiitaにちょうど詳しい解説があったので、詳細はそちらを見てください。
が、やっぱり必要性はよくわからん。
Nodeなら使い道があるのかな?
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
メソッドが呼ばれてコンソールにログが吐かれます。
ドキュメントには『重要なロジックには使うな』とか書かれてあってどうすんだこれ。
感想
JavaScriptのドラスティックな変化ってのはだいたいES2017-2018あたりでやり尽くされたかんじがあり、それ以降は"ちょっとした便利な機能の追加"ってのが多い気がしますね。
ES2021も、文法が大きく変わるような変更は入っておらず、今後書くのがちょっと楽になるようなものがメインです。
もちろんそれぞれの追加機能も便利ではあるのですが、せっかくだから何かasync/awaitレベルの革新的な機能もほしいところですね。