
Overview
Ecma-262 has just been released on July 1st. I’m eager to dive into the features most of which have already been implemented (i.e. released prior to the formal release announcement). Here they are with more information from the author around the motivation each feature to be added and proposed usecase:
- Math.sumPrecise()
- Iterator.concat(…iterators)
- Array.fromAsync()
- Error.isError()
- Map.getOrInsert() – also works with WeakMap
- Uint8Array.toBase64(), Uint8Array.fromBase64(), Uint8Array.toHex(), Uint8Array.fromHex()
- JSON.parse() source text access
- JSON.rawJSON()
Also featuring a sneak peek at upcoming features in 2027. new Temporal() is my favorite!
Math.sumPrecise()
Motivation:
The sum of numbers is one of the last common reasons to reach for Array.prototype.reduce, so this release features a dedicated function (Math.sumPrecise()) to sum all values in an array/iterable of numbers using the Shewchuk ’96 algorithm (I had to duckduckgo it, too) which solves the problem of floating point precision loss cumulatively over large arrays while it still returns the famous IEEE 754 floating number. Yet, no luck summing 0.1 + 0.2 ;)
Proposal:
let values = [1e20, 0.1, -1e20];
values.reduce((a, b) => a + b, 0); // 0
Math.sumPrecise(values); // 0.1
Iterator.concat(…iterators)
Motivation:
More declarative way to consume two or more iterators in sequence as if they were one.
Proposal:
let lows = Iterator.from([0, 1, 2, 3]);
let highs = Iterator.from([6, 7, 8, 9]);
let digits = Iterator.concat(lows, [4, 5], highs);
Array.from(digits); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Array.fromAsync()
Motivation:
The Array.from() async counterpart where an async iterator can be dumped instead of the usual for await loop. Demand is proven – the it-all npm package has 200K+ weekly downloads.
Proposal:
// Replace:
const result = [];
for await (const element of items) {
result.push(element);
}
// With:
Array.fromAsync(items)
Error.isError()
Motivation:
There is no reliable way to test whether a value is a genuine Error:instanceof Error returns false negatives Object.prototype.toString.call(x) === '[object Error]' is spoofable since Symbol.toStringTag was introduced
Proposal:
Error.isError(errorInstance); // returns true or false (analogous to Array.isArray(value)
Note: MDN warns this is not fully compatible yet for Safari (desktop and mobile) as of today.
Upsert (i.e. Map/WeakMap.getOrInsert())
Motivation:
A common problem when using Map or WeakMap is how to handle doing an update when you’re not sure if the key already exists in the map. This is currently handled by checking if the key is present and then inserting or updating depending on the case. This is suboptimal and this feature exists to fix that.
Proposal:
// Currently
let prefs = getUserPrefsMap();
if (!prefs.has("useDarkmode")) {
prefs.set("useDarkmode", true); // default to true
}
// Using getOrInsert
let prefs = getUserPrefsMap();
prefs.getOrInsert("useDarkmode", true); // default to true
This proposal features another addition. The addition of getOrInsertComputed() for some specific cases:
// Using getOrInsertComputed
let grouped = new Map();
for (let [key, ...values] of data) {
grouped.getOrInsertComputed(key, () => []).push(...values);
}
Uint8Array to/from base64 and hex
Motivation:
There is no specialized way to encode and decode to and from base64 and hex. This feature addresses that.
Proposal:
// Basic API:
let arr = new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]);
console.log(arr.toBase64());
// 'SGVsbG8gV29ybGQ='
console.log(arr.toHex());
// '48656c6c6f20576f726c64'
let string = 'SGVsbG8gV29ybGQ=';
console.log(Uint8Array.fromBase64(string));
// Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100])
string = '48656c6c6f20576f726c64';
console.log(Uint8Array.fromHex(string));
// Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100])
Check out this MDN article for additional toBase64() options.
JSON.parse() source text access
Motivation:
Addresses a decades-old problem of parsing JSON strings. Transformation between ECMAScript values and JSON text is lossy. This is most obvious in the case of de-serializing numbers (e.g., “999999999999999999“, “999999999999999999.0“, and “1000000000000000000” all parse to 1000000000000000000), but also comes up when attempting to round-trip non-primitive values such as Date objects (e.g., JSON.parse(JSON.stringify(new Date("2018-09-25T14:00:00Z"))) yields a string "2018-09-25T14:00:00.000Z").
Proposal:
// Illustrative examples:
const digitsToBigInt = (key, val, {source}) =>
/^[0-9]+$/.test(source) ? BigInt(source) : val;
const bigIntToRawJSON = (key, val) =>
typeof val === "bigint" ? JSON.rawJSON(String(val)) : val;
const tooBigForNumber = BigInt(Number.MAX_SAFE_INTEGER) + 2n;
JSON.parse(String(tooBigForNumber), digitsToBigInt) === tooBigForNumber;
// → true
const wayTooBig = BigInt("1" + "0".repeat(1000));
JSON.parse(String(wayTooBig), digitsToBigInt) === wayTooBig;
// → true
const embedded = JSON.stringify({ tooBigForNumber }, bigIntToRawJSON);
embedded === '{"tooBigForNumber":9007199254740993}';
// → true
JSON.rawJSON()
The JSON.rawJSON() static method creates a “raw JSON” object containing a piece of JSON text. When serialized to JSON, the raw JSON object is treated as if it is already a piece of JSON. This text is required to be valid JSON.
I didn’t find that in the proposals list, hence I cannot provide motivation for this change. More information can be found on the JSON.rawJSON() MDN page.
Sneak peek at ECMAScript 2027:
- Temporal – provides standard objects and functions for working with dates and times with principles like immutability, all time-of-day values to be based on a standard 24-hour clock and leap seconds won’t be represented. More information on Maggie Pint’s blog post.
- Explicit resource management – novel syntax for explicit management of various resources like memory, I/O, etc..
- Atomics.pause – Efficient implementation of the locks uses a loop body of the following shape for acquiring the lock
- Joint iteration – or “zip”. Two or more iterators that are positionally aligned (the first value yielded by the first iterator corresponds to the first value yielded by the other iterators, and so on), and you would like to operate on the corresponding values together.
There are exciting times ahead of us.