What’s New in ECMAScript? A Deep Dive into Stage 3‑4 TC39 Proposals
This article reviews the latest TC39 proposals advancing from Stage 3 to Stage 4—including Promise.any, AggregateError, WeakRefs, FinalizationRegistry, logical assignment operators, numeric literal separators, Intl.ListFormat, Intl.DateTimeFormat styles, iterator.item, Intl.Segmenter, Record & Tuple, JSON.parse source‑text access, await extensions, Array.prototype.unique, and resizable array buffers—explaining their goals, required thresholds, and providing concrete code examples.
Stage 3 → Stage 4
To move from Stage 3 to Stage 4 a proposal must satisfy several criteria:
Provide a complete tc39/test262 test suite that validates compatibility across JavaScript engines and transpilers.
At least two implementations must pass the Test262 suite and ship the feature in a released version.
Submit a pull request to tc39/ecma262 that incorporates the proposal into the official ECMAScript specification, signed off by the ECMAScript editor.
Promise.any & AggregateError
Proposal link: https://github.com/tc39/proposal-promise-any
Promise.any resolves as soon as any input promise resolves; if all promises reject, it rejects with an AggregateError that aggregates the individual errors via the AggregateError.errors property.
Promise.any([
fetch('https://example.com/').then(() => 'home'),
fetch('https://example.com/blog').then(() => 'blog'),
fetch('https://example.com/docs').then(() => 'docs')
]).then(first => {
console.log(first); // → 'home'
}).catch(error => {
console.log(error);
});WeakRefs & FinalizationRegistry
Proposal link: https://github.com/tc39/proposal-weakrefs/
WeakRefs allow weak references to objects without preventing garbage collection. FinalizationRegistry provides a callback mechanism (similar to a destructor) that receives a heldValue identifying the reclaimed object.
const cache = new Map();
const finalizationGroup = new FinalizationRegistry(name => {
const ref = cache.get(name);
if (ref !== undefined && ref.deref() === undefined) {
cache.delete(name);
}
});
function getImageCached(name) {
const ref = cache.get(name);
if (ref !== undefined) {
const deref = ref.deref();
if (deref !== undefined) return deref;
}
const image = performExpensiveOperation(name);
const wr = new WeakRef(image);
cache.set(name, wr);
finalizationGroup.register(image, name);
return image;
}Logical Assignment
Proposal link: https://github.com/tc39/proposal-logical-assignment
Introduces three new operators: a ||= b (logical OR assignment) a &&= b (logical AND assignment) a ??= b (nullish coalescing assignment)
// Or assignment
a ||= b; // equivalent to a || (a = b)
// And assignment
a &&= b; // equivalent to a && (a = b)
// Nullish assignment
a ??= b; // equivalent to a ?? (a = b)
function example(opts) {
opts.foo = opts.foo ?? 'bar';
opts.baz ??= 'qux';
}Numeric Literal Separator
Proposal link: https://github.com/tc39/proposal-numeric-separator
Allows the underscore ( _) as a visual separator in numeric literals to improve readability.
10_0000_0000; // 1 000 000 000
let fee = 123_00; // 12 300 (cents)
let amount = 1_234_500; // 1,234,500
0.000_001; // 1 µ
1e10_000; // 10^(10 000)Intl.ListFormat
Proposal link: https://github.com/tc39/proposal-intl-list-format
Provides locale‑aware list formatting.
let lfmt = new Intl.ListFormat('zh', {type: 'conjunction', style: 'long'});
console.log(lfmt.format(['Anne', 'John', 'Mike'])); // "Anne、John和Mike"Intl.DateTimeFormat dateStyle/timeStyle
Proposal link: https://github.com/tc39/proposal-intl-datetime-style
Introduces dateStyle and timeStyle options for locale‑appropriate formatting without manually specifying individual components.
let dtf = new Intl.DateTimeFormat('zh', {dateStyle: 'short'});
console.log(dtf.format(new Date())); // "2020/7/27"
let dtf2 = new Intl.DateTimeFormat('en-US', {dateStyle: 'short'});
console.log(dtf2.format(new Date())); // "7/27/20"iterator.items()
Proposal link: https://github.com/tabatkins/proposal-item-method
Provides an .item method on any indexable object (Array, String, TypedArray) to retrieve elements using positive or negative indices, similar to Python’s negative indexing.
let arr = [10, 20, 30];
console.log(arr.item(-1)); // 30
console.log('hello'.item(-2)); // 'l'Intl.Segmenter
Proposal link: https://github.com/tc39/proposal-intl-segmenter
Implements Unicode Text Segmentation (UAX 29) for word, sentence, and grapheme segmentation.
let segmenter = new Intl.Segmenter('zh', {granularity: 'word'});
let input = "我不是,我没有,你别瞎说。";
for (let {segment, index, isWordLike} of segmenter.segment(input)) {
console.log(`segment at ${index}: «${segment}»${isWordLike ? ' (word‑like)' : ''}`);
}Record and Tuple
Proposal link: https://github.com/tc39/proposal-record-tuple
Introduces immutable primitive types Record (object‑like) and Tuple (array‑like) with value‑based equality.
const grid = new Map([
[#[0,0], 'player'],
[#{x:3, y:5}, 'enemy']
]);
console.log(grid.get(#[0,0])); // player
console.log(grid.get({x:3, y:5})); // undefinedJSON.parse source‑text access
Proposal link: https://github.com/tc39/proposal-json-parse-with-source
Extends JSON.parse reviver and JSON.stringify replacer with access to the raw source text and a rawTag symbol, enabling precise handling of large numbers and custom types.
// Reviver can read the original source
JSON.parse(' 9999999999999999', (k, v, {source}) => BigInt(source)); // → 9999999999999999n
// Replacer can emit raw source for custom serialization
JSON.stringify(9999999999999999n, (k, v, {rawTag}) => ({[rawTag]: String(v)})); // → "9999999999999999"await operations
Proposal link: https://github.com/tc39-transfer/proposal-await.ops
Proposes syntactic sugar such as await.all, await.race, await.any to make common Promise combinators more readable.
// before
await Promise.all(users.map(async x => fetchProfile(x.id)));
// after
await.all users.map(async x => fetchProfile(x.id));Array.prototype.unique()
Proposal link: https://github.com/TechQuery/array-unique-proposal
Adds a unique method that can deduplicate complex objects using an optional key‑selector function.
arr.unique(); // similar to [...new Set(arr)]
arr.unique(x => x.id); // deduplicate by idResizableArrayBuffer and GrowableSharedArrayBuffer
Proposal link: https://github.com/syg/proposal-resizablearraybuffer
Defines two new buffer types:
ResizableArrayBuffer : can be grown or shrunk in place (implementation‑defined observation behavior).
GrowableSharedArrayBuffer : shared across agents, can only grow.
let rab = new ResizableArrayBuffer(1024, 1024**2);
assert(rab.byteLength === 1024);
assert(rab.maximumByteLength === 1024**2);
rab.resize(rab.byteLength * 2);
assert(rab.byteLength === 2048);Stage Transition Criteria
General thresholds for moving between stages:
Stage 2 → Stage 3: full specification text reviewed and signed by a TC39 member, plus editor approval.
Stage 1 → Stage 2: a champion, problem statement, examples, and initial design discussion.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
