12k
All articles

ECMAScript 2026 の新機能

ES2026はJavaScriptに7つの新機能を追加。Array.fromAsync、Math.sumPrecise、Uint8Arrayのbase64とhex、JSON.parseのソースアクセス、getOrInsertを解説します。

OpenReplay Team
OpenReplay Team
ECMAScript 2026 の新機能

Ecma International は 2026 年 6 月 30 日に ECMAScript 2026 言語仕様を承認しました。そこに含まれる新機能はちょうど 7 つです。Array.fromAsyncError.isErrorMath.sumPreciseUint8Array の base64 および hex メソッド、Iterator.concatJSON.parse のソーステキストアクセス、そして Map と WeakMap の getOrInsert です。

毎年これらのメソッドは静かに追加され、私たちの多くは何年も抱え続けてきたヘルパー関数を削除する日に初めてそれらと出会います。今回はおそらく base64 ユーティリティでしょう。

最近 ES2026 について検索したことがあるなら、互いに矛盾するリストに行き当たったはずです。その中には Temporal や using キーワードを含んでいるものもいくつかあります。それらのリストは誤りです。本記事ではその理由を説明したうえで、実際に導入された 7 つの機能それぞれについて、解決する課題、置き換えられるコード、そしてそれを置き換える 1 行のコードを見ていきます。

信頼できる情報源は Ecma の承認アナウンスで、ES2026 が ECMA-262 の第 17 版であることを確認できます。また TC39 の finished-proposals テーブルでは、Expected Publication Year が 2026 となっている行はちょうど 7 つです。

要点

  • ES2026 は 2026 年 6 月 30 日に承認され、ちょうど 7 つの機能を追加します。Temporal と Explicit Resource Management はその中に含まれません。
  • Math.sumPrecise([1e17, 1, -1e17])1 を返しますが、同じ値を reduce で合計すると 0 になります。
  • Uint8Array に組み込みの toBase64()toHex()fromBase64()fromHex() が追加され、よくある依存関係を 1 つ削除できます。
  • JSON.parse の reviver は生のソーステキストを公開する第 3 引数を受け取るようになり、JSON.rawJSON() が stringify 側を補完するため、BigInt が JSON をラウンドトリップできるようになります。
  • Map.prototype.getOrInsert(key, value) は、キーがすでに存在する場合には既存の値を返します。デフォルト値が挿入されるのはキーが存在しない場合のみです。

ES2026 に入らなかったものは何か?

Temporal と Explicit Resource Management(using / await using)は ES2026 の一部ではありません。TC39 の finished-proposals リストでは、どちらも Atomics.pause や Joint Iteration とともに Expected Publication Year が 2027 となっています。この 4 つはいずれも、ES2026 の締め切り後に TC39 プロセスの最終段階である stage 4 に到達しました。2025 年後半の承認前の報道では、これらが 2026 年に入ると広く予想されており、それが矛盾する機能リストの出所です。誰でも実行できる確認方法は、finished-proposals テーブルの Expected Publication Year 列でフィルタリングすることです。7 つの提案が 2026 となっており、そのいずれも Temporal ではありません。

Array.fromAsync

Array.fromAsync は非同期イテレータを受け取り、Promise でラップされた配列を返します。Array.from と同様に、値が Promise である同期イテレータ、配列風オブジェクト、そして任意のマッピング関数も受け付けます。これまで非同期イテレータを収集するには、for await ループでミュータブルな配列に push する必要がありました。

const arr = [];
for await (const n of asyncGen(1, 3)) {
  arr.push(`page ${n}`);
}

同じ結果が、今では単一の式で書けます。

const arr = await Array.fromAsync(asyncGen(1, 3), (n) => `page ${n}`);
// [ 'page 1', 'page 2', 'page 3' ]

Error.isError

Error.isError(value) は、キャッチした値が本物の Error オブジェクトである場合にのみ true を返します。JavaScript では何でも throw できますし、instanceof Error は値が iframe や realm の境界を越えると誤った結果を返します。realm ごとに固有の Error コンストラクタを持つためです。instanceof Error で条件分岐している catch ブロックは、有用なメッセージを表示する分岐を暗黙のうちにスキップしてしまう可能性があり、この種のバグのセッションリプレイには見覚えのあるパターンが現れます。裏で実際に throw が発生しているにもかかわらず、何も表示されない画面でユーザーが立ち往生している、というものです。Error.isError はこのギャップを埋めます。

try {
  throw new Error("session upload failed");
} catch (error) {
  Error.isError(error); // true
}

try {
  throw "session upload failed";
} catch (error) {
  Error.isError(error); // false
}

Math.sumPrecise

Math.sumPrecise は、中間結果に浮動小数点誤差を蓄積させることなく、数値のイテラブルを合計します。reduce チェーンは 2 つずつ加算していくため、大きな値と小さな値が混在すると桁落ちが深刻になります。

const values = [1e17, 1, -1e17];

values.reduce((a, b) => a + b, 0); // 0
Math.sumPrecise(values);           // 1

reduce 版では 1 が完全に失われます。精度の問題に加えて、これは定型的な sum reducer も不要にします。

Uint8Array の toBase64、toHex、fromBase64、fromHex

ES2026 では Uint8Array に組み込みの toBase64() および toHex() メソッドが追加され、デコード用に静的メソッドの Uint8Array.fromBase64()Uint8Array.fromHex() も用意されます。バイナリとテキストの変換にはこれまでライブラリの導入や自前実装が必要でしたが、これは多くのコードベースが今や削除できる依存関係です。

const value = new Uint8Array([79, 112, 101, 110, 82, 101, 112, 108, 97, 121]);

const valueBase64 = value.toBase64(); // 'T3BlblJlcGxheQ=='
const decoded = Uint8Array.fromBase64(valueBase64);

new TextDecoder().decode(decoded); // 'OpenReplay'

Iterator.concat

Iterator.concat() はイテレータを順番に連結します。これまでは、各ソースを走査して yield* で委譲する手書きのジェネレータを書くしかありませんでした。

function* combine(...iterators) {
  for (const source of iterators) {
    yield* source;
  }
}

今ではこれが 1 回の呼び出しで済み、値のプレーンな配列をイテレータの間に挟み込むこともできます。以下の中央にある [300] はプレーンな配列であり、Iterator.from でラップする必要はありません。

const iOne = Iterator.from([100, 200]);
const iTwo = Iterator.from([400, 500]);

const combined = Iterator.concat(iOne, [300], iTwo);
Array.from(combined); // [ 100, 200, 300, 400, 500 ]

JSON.parse のソーステキストアクセス

ES2026 以前、JavaScript における JSON のラウンドトリップは双方向とも情報が失われるものでした。大きな整数リテラルをパースすると暗黙のうちに丸められ、BigInt を stringify すると例外が投げられます。

JSON.parse("9007199254740993");   // 9007199254740992
JSON.stringify(9007199254740993n); // TypeError: Do not know how to serialize a BigInt

JSON.parse source text access proposal は双方の問題を解決します。JSON.parse は reviver に、手を加えられていない source テキストを保持する第 3 引数を渡すようになり、桁を失うことなく自分で変換できます。stringify 側では、JSON.rawJSON() によって replacer が値をそのまま出力できます。

JSON.parse("9007199254740993", (key, value, { source }) => BigInt(source));
// 9007199254740993n

JSON.stringify(9007199254740993n, (key, value) => JSON.rawJSON(value));
// 9007199254740993

これで BigInt は、桁を失うことも例外を投げることもなく JSON のラウンドトリップを生き延びられます。

Map.prototype.getOrInsert と WeakMap.prototype.getOrInsert

Upsert proposalMapWeakMap の両方に getOrInsert(key, value) をもたらし、has() を呼んでから set() を呼ぶ必要がなくなります。セマンティクスが重要です。getOrInsert はキーがすでに存在する場合、既存の値を返します。挿入が行われ、デフォルト値が返されるのは、キーが存在しない場合のみです。

const limits = new Map();
limits.set("uploads", 25);

limits.getOrInsert("exports", 10);   // 10  (inserted)
limits.getOrInsert("uploads", 100);  // 25  (existing value wins)

"uploads" はすでに設定されているため、100 は破棄される点に注意してください。第 2 引数は上書きではなくデフォルト値として扱いましょう。

今すぐ削除できるものは?

ES2026 はクリーンアップのためのリリースです。7 つの機能はいずれも、おそらく今日あなたの本番環境にあるパターンを不要にします。base64 ヘルパーの依存関係、sum reducer、has() してから set() するガードブロック、yield* による連結ジェネレータ、そして for await による収集ループには、すべて直接的な組み込みの代替手段があります。ランタイムでの利用可否はエンジンによって異なるため、ポリフィルを外す前に MDN の標準組み込みオブジェクトリファレンスで各メソッドの現在のサポート状況を確認してください。そのうえで、コードベースの中で最も簡単な改善から始めましょう。多くのチームにとってそれは、Map の存在チェックを getOrInsert に置き換えることです。

FAQ

Array.fromAsync と Promise.all の違いは何ですか?

Array.fromAsync は値を 1 つずつ逐次的に await し、非同期イテラブル、Promise を生成する同期イテラブル、配列風オブジェクトを受け付けます。Promise.all は Promise のイテラブルを並行に実行し、いずれかの Promise が reject された時点で即座に reject します。並列処理には Promise.all を、順序が重要な場合やソースが非同期イテレータである場合には Array.fromAsync を使用してください。どちらも配列に解決される Promise を返します。

Error.isError は TypeError やその他の Error サブクラスに対して true を返しますか?

はい。Error.isError は TypeError、RangeError、SyntaxError、および Error を継承したカスタムクラスを含め、あらゆる本物の Error サブクラスに対して true を返します。これはエンジンがプロトタイプチェーンではなく、本物のエラーが持つ内部ブランドをチェックするためです。ブラウザでは DOMException も本物のエラーとして扱われます。message プロパティを持つだけのプレーンオブジェクトや、Object.create(Error.prototype) で作成されたオブジェクトなど、単にエラーを模倣しただけのオブジェクトに対しては false を返します。

Uint8Array.prototype.toBase64 は URL セーフな base64 を生成できますか?

はい。toBase64 は、alphabet プロパティに 'base64'(デフォルト、+ と / を使用)または 'base64url'(- と _ を使用)を設定できるオプションオブジェクトと、末尾の = 文字を除去する omitPadding ブール値を受け付けます。Uint8Array.fromBase64 は同じ alphabet オプションに加え、4 文字未満の最終チャンクをどうデコードするかを制御する lastChunkHandling オプション('loose'、'strict'、'stop-before-partial')を受け付けます。

getOrInsert でコストの高いデフォルト値の計算を避けるにはどうすればよいですか?

同じ Upsert proposal が Map と WeakMap の両方に追加する getOrInsertComputed(key, callback) を使用してください。キーが存在しない場合にのみコールバックを呼び出して値を生成し、その結果を挿入して返します。キーがすでに存在する場合、コールバックは実行されず既存の値が返されます。通常の getOrInsert は、キーがすでに存在する場合であっても、呼び出し前に必ず第 2 引数を評価します。

Open-source session replay

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.