Table of Contents
Web Development10 min read2025-06-12
JavaScript ES2024Features
The latest JavaScript features and their practical application – a comprehensive guide for modern JavaScript development with ES2024.
JavaScriptES2024Modern JSECMAScriptWeb APIs
JavaScript ES2024 Overview
ECMAScript 2024 (ES15) introduces new features that make JavaScript development more efficient and expressive. This version focuses on array manipulation, RegExp improvements, and new Promise methods.
- New Array Methods: groupBy, toSorted, toReversed, toSpliced
- RegExp Improvements: Named Capture Groups, Unicode Sets
- Promise.withResolvers(): New Promise constructor alternative
- Performance: Optimized implementations for better performance
New Array Methods
Array.prototype.toSorted():
// Non-mutating sort
const numbers = [3, 1, 4, 1, 5, 9];
// Old: Mutates the original array
const sortedOld = numbers.sort(); // numbers is changed!
// New: Returns a new array
const sortedNew = numbers.toSorted(); // numbers remains unchanged
console.log(numbers); // [3, 1, 4, 1, 5, 9] (unchanged)
console.log(sortedNew); // [1, 1, 3, 4, 5, 9]
// With custom comparator
const users = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Charlie', age: 35 }
];
const sortedByAge = users.toSorted((a, b) => a.age - b.age);
console.log(sortedByAge);
// [{ name: 'Bob', age: 25 }, { name: 'Alice', age: 30 }, { name: 'Charlie', age: 35 }]
// Functional chaining
const processedData = data
.filter(item => item.active)
.toSorted((a, b) => a.priority - b.priority)
.map(item => ({ ...item, processed: true }));Array.prototype.toReversed():
// Non-mutating reverse
const items = ['a', 'b', 'c', 'd'];
// Old: Mutates the original array
const reversedOld = items.reverse(); // items is changed!
// New: Returns a new array
const reversedNew = items.toReversed(); // items remains unchanged
console.log(items); // ['a', 'b', 'c', 'd'] (unchanged)
console.log(reversedNew); // ['d', 'c', 'b', 'a']
// Practical example: Breadcrumb navigation
function createBreadcrumb(path) {
return path
.split('/')
.filter(Boolean)
.toReversed()
.map((segment, index) => ({
name: segment,
isLast: index === 0
}));
}
console.log(createBreadcrumb('/wiki/javascript/es2024'));
// [
// { name: 'es2024', isLast: true },
// { name: 'javascript', isLast: false },
// { name: 'wiki', isLast: false }
// ]Array.prototype.toSpliced():
// Non-mutating splice operation
const items = ['apple', 'banana', 'cherry', 'date'];
// Old: Mutates the original array
const splicedOld = items.splice(1, 2, 'orange', 'grape'); // items is changed!
// New: Returns a new array
const splicedNew = items.toSpliced(1, 2, 'orange', 'grape'); // items remains unchanged
console.log(items); // ['apple', 'banana', 'cherry', 'date'] (unchanged)
console.log(splicedNew); // ['apple', 'orange', 'grape', 'date']
// Practical example: Todo list update
class TodoList {
constructor() {
this.todos = [];
}
addTodo(todo, index = this.todos.length) {
this.todos = this.todos.toSpliced(index, 0, todo);
return this;
}
removeTodo(index) {
this.todos = this.todos.toSpliced(index, 1);
return this;
}
updateTodo(index, updatedTodo) {
this.todos = this.todos.toSpliced(index, 1, updatedTodo);
return this;
}
}
const todoList = new TodoList();
todoList
.addTodo({ id: 1, text: 'Learn ES2024', done: false })
.addTodo({ id: 2, text: 'Write article', done: false })
.updateTodo(0, { id: 1, text: 'Learn ES2024', done: true });Array.prototype.with():
// Non-mutating index-based replacement
const fruits = ['apple', 'banana', 'cherry'];
// Old: Mutation via index assignment
fruits[1] = 'orange'; // fruits is changed!
// New: Returns a new array
const newFruits = fruits.with(1, 'orange'); // fruits remains unchanged
console.log(fruits); // ['apple', 'banana', 'cherry'] (unchanged)
console.log(newFruits); // ['apple', 'orange', 'cherry']
// Negative indices supported
const lastChanged = fruits.with(-1, 'grape');
console.log(lastChanged); // ['apple', 'banana', 'grape']
// Practical example: State update in React
function useArrayState(initialArray) {
const [array, setArray] = useState(initialArray);
const updateItem = useCallback((index, newValue) => {
setArray(prevArray => prevArray.with(index, newValue));
}, []);
const updateLastItem = useCallback((newValue) => {
setArray(prevArray => prevArray.with(-1, newValue));
}, []);
return [array, { updateItem, updateLastItem }];
}RegExp Improvements
Unicode Set Notation (v-Flag):
// New v-flag for extended Unicode support
// Replaces the u-flag with more features
// Character class subtraction
const asciiLettersOnly = /^[p{ASCII}--p{Cc}]+$/v;
console.log(asciiLettersOnly.test('Hello')); // true
console.log(asciiLettersOnly.test('Hëllo')); // false
// String literals in character classes
const emojiPattern = /[q{||}]/v;
console.log(emojiPattern.test('')); // true
console.log(emojiPattern.test('')); // true
// Intersection of character classes
const hexDigits = /[p{ASCII_Hex_Digit}&&[0-9A-F]]/v;
console.log(hexDigits.test('A')); // true
console.log(hexDigits.test('G')); // false
// Practical example: Email validation
const emailPattern = /^[p{L}p{N}._%-]+@[p{L}p{N}.-]+.[p{L}]{2,}$/v;
function validateEmail(email) {
return emailPattern.test(email);
}
console.log(validateEmail('user@example.com')); // true
console.log(validateEmail('üser@dömail.com')); // true (Unicode support)
// Extended Unicode property escapes
const modernTextPattern = /^[p{L}p{N}p{M}p{Z}p{P}&&[^p{Cc}]]+$/v;
function isValidModernText(text) {
return modernTextPattern.test(text);
}Named Capture Groups Enhancements:
// Enhanced named capture groups
const datePattern = /(?<year>d{4})-(?<month>d{2})-(?<day>d{2})/;
const urlPattern = /^(?<protocol>https?)://(?<host>[^/]+)(?<path>/.*)?$/;
// Destructuring with named groups
function parseDate(dateString) {
const match = dateString.match(datePattern);
if (!match) return null;
const { year, month, day } = match.groups;
return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
}
console.log(parseDate('2024-06-12')); // Date object
// URL parser with named groups
function parseURL(url) {
const match = url.match(urlPattern);
if (!match) return null;
return {
protocol: match.groups.protocol,
host: match.groups.host,
path: match.groups.path || '/',
port: match.groups.host.includes(':') ?
match.groups.host.split(':')[1] :
(match.groups.protocol === 'https' ? '443' : '80')
};
}
console.log(parseURL('https://example.com/path'));
// { protocol: 'https', host: 'example.com', path: '/path', port: '443' }
// Template string replacement with named groups
const template = 'Hello {{name}}, you have {{count}} messages';
const templatePattern = /{{(?<key>w+)}}/g;
function renderTemplate(template, data) {
return template.replace(templatePattern, (match, key) => {
return data[key] || match;
});
}
console.log(renderTemplate(template, { name: 'Alice', count: 5 }));
// "Hello Alice, you have 5 messages"Promise.withResolvers()
Promise.withResolvers() Basics:
// New way to create Promises with external resolve/reject
// Replaces the usual Promise constructor pattern
// Old: Promise constructor pattern
function createPromiseOld() {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
// New: Promise.withResolvers()
function createPromiseNew() {
return Promise.withResolvers();
}
const { promise, resolve, reject } = Promise.withResolvers();
// Resolve promise later
setTimeout(() => {
resolve('Success!');
}, 1000);
promise.then(console.log); // "Success!" after 1 secondPractical Applications:
// Event-based promise resolution
class EventPromise {
constructor() {
this.promises = new Map();
}
waitFor(eventName) {
if (!this.promises.has(eventName)) {
const { promise, resolve } = Promise.withResolvers();
this.promises.set(eventName, { promise, resolve });
}
return this.promises.get(eventName).promise;
}
emit(eventName, data) {
if (this.promises.has(eventName)) {
const { resolve } = this.promises.get(eventName);
resolve(data);
this.promises.delete(eventName);
}
}
}
const eventManager = new EventPromise();
// Wait for event
eventManager.waitFor('user-login').then(user => {
console.log('User logged in:', user);
});
// Trigger event later
setTimeout(() => {
eventManager.emit('user-login', { id: 1, name: 'Alice' });
}, 2000);
// Async/await resource loader
class ResourceLoader {
constructor() {
this.cache = new Map();
this.loading = new Map();
}
async load(url) {
// Cache hit
if (this.cache.has(url)) {
return this.cache.get(url);
}
// Already loading
if (this.loading.has(url)) {
return this.loading.get(url).promise;
}
// Start loading
const { promise, resolve, reject } = Promise.withResolvers();
this.loading.set(url, { promise, resolve, reject });
try {
const response = await fetch(url);
const data = await response.json();
this.cache.set(url, data);
this.loading.delete(url);
resolve(data);
return data;
} catch (error) {
this.loading.delete(url);
reject(error);
throw error;
}
}
}
const loader = new ResourceLoader();
// Multiple concurrent requests for the same resource
// are automatically deduplicated
Promise.all([
loader.load('/api/users'),
loader.load('/api/users'), // Uses the same promise
loader.load('/api/users') // Uses the same promise
]).then(results => {
console.log('All loaded:', results);
});
// WebSocket promise wrapper
class WebSocketPromise {
constructor(url) {
this.ws = new WebSocket(url);
this.pendingRequests = new Map();
this.ws.onmessage = (event) => {
const { id, data, error } = JSON.parse(event.data);
if (this.pendingRequests.has(id)) {
const { resolve, reject } = this.pendingRequests.get(id);
this.pendingRequests.delete(id);
if (error) {
reject(new Error(error));
} else {
resolve(data);
}
}
};
}
send(data) {
const id = Math.random().toString(36).substr(2, 9);
const { promise, resolve, reject } = Promise.withResolvers();
this.pendingRequests.set(id, { resolve, reject });
this.ws.send(JSON.stringify({ id, data }));
return promise;
}
}
const wsClient = new WebSocketPromise('ws://localhost:8080');
// WebSocket request as promise
wsClient.send({ action: 'getUser', userId: 123 })
.then(user => console.log('User:', user))
.catch(error => console.error('Error:', error));Temporal API (Preview)
The Temporal API is still in Stage 3 but is expected to be finalized soon. It solves many issues with the Date object.
Temporal Basics:
// Temporal API (when available)
// Modern, immutable Date/Time API
// PlainDate for date operations without time
const today = Temporal.PlainDate.from('2024-06-12');
const tomorrow = today.add({ days: 1 });
const lastWeek = today.subtract({ weeks: 1 });
console.log(today.toString()); // "2024-06-12"
console.log(tomorrow.toString()); // "2024-06-13"
console.log(lastWeek.toString()); // "2024-06-05"
// PlainTime for time without date
const noon = Temporal.PlainTime.from('12:00:00');
const inOneHour = noon.add({ hours: 1 });
console.log(noon.toString()); // "12:00:00"
console.log(inOneHour.toString()); // "13:00:00"
// ZonedDateTime for full date/time with timezone
const meeting = Temporal.ZonedDateTime.from({
year: 2024,
month: 6,
day: 12,
hour: 14,
minute: 30,
timeZone: 'Europe/Berlin'
});
const meetingInNY = meeting.withTimeZone('America/New_York');
console.log(meeting.toString()); // "2024-06-12T14:30:00+02:00[Europe/Berlin]"
console.log(meetingInNY.toString()); // "2024-06-12T08:30:00-04:00[America/New_York]"
// Duration for time spans
const duration = Temporal.Duration.from({ hours: 2, minutes: 30 });
const endTime = meeting.add(duration);
console.log(endTime.toString()); // "2024-06-12T17:00:00+02:00[Europe/Berlin]"
// Comparisons are easier and more reliable
const date1 = Temporal.PlainDate.from('2024-06-12');
const date2 = Temporal.PlainDate.from('2024-06-13');
console.log(Temporal.PlainDate.compare(date1, date2)); // -1 (date1 < date2)
console.log(date1.equals(date2)); // false
console.log(date1.since(date2).toString()); // "-P1D" (1 day less)Practical Temporal Applications:
// Business logic with Temporal
class BusinessCalendar {
constructor(timeZone = 'Europe/Berlin') {
this.timeZone = timeZone;
this.workingHours = {
start: Temporal.PlainTime.from('09:00'),
end: Temporal.PlainTime.from('17:00')
};
this.workingDays = [1, 2, 3, 4, 5]; // Mon-Fri
}
isWorkingDay(date) {
const plainDate = Temporal.PlainDate.from(date);
return this.workingDays.includes(plainDate.dayOfWeek);
}
getNextWorkingDay(date) {
let nextDay = Temporal.PlainDate.from(date).add({ days: 1 });
while (!this.isWorkingDay(nextDay)) {
nextDay = nextDay.add({ days: 1 });
}
return nextDay;
}
scheduleDelivery(orderDate, deliveryDays = 3) {
let currentDate = Temporal.PlainDate.from(orderDate);
let workingDaysAdded = 0;
while (workingDaysAdded < deliveryDays) {
currentDate = this.getNextWorkingDay(currentDate);
workingDaysAdded++;
}
return currentDate;
}
getWorkingHoursInTimeZone(date, targetTimeZone) {
const startDateTime = date.toZonedDateTime({
timeZone: this.timeZone,
plainTime: this.workingHours.start
});
const endDateTime = date.toZonedDateTime({
timeZone: this.timeZone,
plainTime: this.workingHours.end
});
return {
start: startDateTime.withTimeZone(targetTimeZone),
end: endDateTime.withTimeZone(targetTimeZone)
};
}
}
const calendar = new BusinessCalendar('Europe/Berlin');
const orderDate = Temporal.PlainDate.from('2024-06-12');
const deliveryDate = calendar.scheduleDelivery(orderDate, 3);
console.log(`Order: ${orderDate}, Delivery: ${deliveryDate}`);
// Recurring events
class RecurringEvent {
constructor(startDate, pattern) {
this.startDate = Temporal.PlainDate.from(startDate);
this.pattern = pattern; // { type: 'weekly', interval: 2 }
}
getOccurrences(count = 10) {
const occurrences = [this.startDate];
let currentDate = this.startDate;
for (let i = 1; i < count; i++) {
switch (this.pattern.type) {
case 'daily':
currentDate = currentDate.add({ days: this.pattern.interval || 1 });
break;
case 'weekly':
currentDate = currentDate.add({ weeks: this.pattern.interval || 1 });
break;
case 'monthly':
currentDate = currentDate.add({ months: this.pattern.interval || 1 });
break;
}
occurrences.push(currentDate);
}
return occurrences;
}
}
const weeklyMeeting = new RecurringEvent('2024-06-12', { type: 'weekly', interval: 1 });
const meetings = weeklyMeeting.getOccurrences(5);
console.log('Weekly meetings:', meetings.map(d => d.toString()));Atomics.waitAsync()
Asynchronous Atomic Operations:
// Atomics.waitAsync() for non-blocking wait operations
// Useful for SharedArrayBuffer and worker communication
// Shared memory setup
const sharedBuffer = new SharedArrayBuffer(1024);
const sharedArray = new Int32Array(sharedBuffer);
// Worker script (worker.js)
// In worker:
self.onmessage = async function(e) {
const { sharedBuffer, index, expectedValue, timeout } = e.data;
const sharedArray = new Int32Array(sharedBuffer);
// Asynchronously wait for value change
const result = Atomics.waitAsync(sharedArray, index, expectedValue, timeout);
if (result.async) {
// Promise-based waiting
const waitResult = await result.value;
if (waitResult === 'ok') {
self.postMessage({
type: 'value-changed',
newValue: Atomics.load(sharedArray, index)
});
} else if (waitResult === 'timed-out') {
self.postMessage({ type: 'timeout' });
}
} else {
// Immediate return
self.postMessage({
type: 'immediate',
result: result.value
});
}
};
// Main thread
class SharedCounter {
constructor() {
this.sharedBuffer = new SharedArrayBuffer(16);
this.sharedArray = new Int32Array(this.sharedBuffer);
this.workers = [];
// Initialize counter
Atomics.store(this.sharedArray, 0, 0);
}
addWorker() {
const worker = new Worker('counter-worker.js');
worker.onmessage = (e) => {
const { type, newValue } = e.data;
if (type === 'value-changed') {
console.log(`Counter changed to: ${newValue}`);
this.notifyListeners(newValue);
}
};
this.workers.push(worker);
return worker;
}
waitForValue(expectedValue, timeout = 5000) {
return new Promise((resolve, reject) => {
const worker = this.addWorker();
worker.postMessage({
sharedBuffer: this.sharedBuffer,
index: 0,
expectedValue,
timeout
});
worker.onmessage = (e) => {
const { type, newValue, result } = e.data;
if (type === 'value-changed') {
resolve(newValue);
} else if (type === 'timeout') {
reject(new Error('Timeout waiting for value'));
} else if (type === 'immediate') {
resolve(result);
}
worker.terminate();
};
});
}
increment() {
const oldValue = Atomics.load(this.sharedArray, 0);
const newValue = Atomics.add(this.sharedArray, 0, 1) + 1;
// Notify all waiting workers
Atomics.notify(this.sharedArray, 0);
return newValue;
}
getValue() {
return Atomics.load(this.sharedArray, 0);
}
}
// Usage
const counter = new SharedCounter();
// Wait for counter to reach 5
counter.waitForValue(5).then(value => {
console.log(`Counter reached ${value}!`);
});
// Increment counter in intervals
setInterval(() => {
const newValue = counter.increment();
console.log(`Counter incremented to: ${newValue}`);
}, 1000);Well-formed Unicode Strings
String.prototype.isWellFormed() and toWellFormed():
// New methods for Unicode validation and correction
// isWellFormed() - Checks for valid Unicode sequences
const validString = 'Hello World';
const invalidString = 'Hello � World'; // Lone surrogate
console.log(validString.isWellFormed()); // true
console.log(invalidString.isWellFormed()); // false
// toWellFormed() - Fixes invalid Unicode sequences
const correctedString = invalidString.toWellFormed();
console.log(correctedString); // "Hello � World" (replacement character)
// Practical application: Secure string processing
class UnicodeValidator {
static validateInput(input) {
if (typeof input !== 'string') {
throw new TypeError('Input must be a string');
}
if (!input.isWellFormed()) {
console.warn('Invalid Unicode sequence detected, auto-correcting...');
return input.toWellFormed();
}
return input;
}
static safeStringify(obj) {
const jsonString = JSON.stringify(obj);
if (!jsonString.isWellFormed()) {
return jsonString.toWellFormed();
}
return jsonString;
}
static safeDatabaseInsert(data) {
// Ensure all string values are well-formed before database insertion
const cleanData = {};
for (const [key, value] of Object.entries(data)) {
if (typeof value === 'string') {
cleanData[key] = UnicodeValidator.validateInput(value);
} else {
cleanData[key] = value;
}
}
return cleanData;
}
}
// Usage examples
const userInput = 'User comment with invalid � surrogate';
const safeInput = UnicodeValidator.validateInput(userInput);
const userData = {
name: 'John Doe',
comment: 'Invalid unicode ��',
email: 'john@example.com'
};
const safeUserData = UnicodeValidator.safeDatabaseInsert(userData);
console.log(safeUserData);
// { name: 'John Doe', comment: 'Invalid unicode ��', email: 'john@example.com' }
// Web API integration
class SafeWebAPI {
static async post(url, data) {
// Ensure all string data is well-formed before sending
const safeData = {};
for (const [key, value] of Object.entries(data)) {
if (typeof value === 'string') {
safeData[key] = value.isWellFormed() ? value : value.toWellFormed();
} else {
safeData[key] = value;
}
}
return fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(safeData)
});
}
static async processResponse(response) {
const text = await response.text();
if (!text.isWellFormed()) {
console.warn('Response contains invalid Unicode, correcting...');
return text.toWellFormed();
}
return text;
}
}Resizable ArrayBuffers
Resizable ArrayBuffer and SharedArrayBuffer:
// Resizable ArrayBuffer - can be resized at runtime
const buffer = new ArrayBuffer(1024, { maxByteLength: 4096 });
const view = new Uint8Array(buffer);
console.log(buffer.byteLength); // 1024
console.log(buffer.maxByteLength); // 4096
console.log(buffer.resizable); // true
// Resize buffer
buffer.resize(2048);
console.log(buffer.byteLength); // 2048
// New view on resized buffer
const newView = new Uint8Array(buffer);
console.log(newView.length); // 2048
// Practical application: Dynamic data buffer
class DynamicBuffer {
constructor(initialSize = 1024, maxSize = 1024 * 1024) {
this.buffer = new ArrayBuffer(initialSize, { maxByteLength: maxSize });
this.view = new Uint8Array(this.buffer);
this.position = 0;
}
ensureCapacity(additionalBytes) {
const requiredSize = this.position + additionalBytes;
if (requiredSize > this.buffer.byteLength) {
const newSize = Math.min(
Math.max(this.buffer.byteLength * 2, requiredSize),
this.buffer.maxByteLength
);
if (newSize > this.buffer.maxByteLength) {
throw new Error('Buffer would exceed maximum size');
}
this.buffer.resize(newSize);
this.view = new Uint8Array(this.buffer);
}
}
writeBytes(data) {
this.ensureCapacity(data.length);
this.view.set(data, this.position);
this.position += data.length;
}
writeString(str) {
const encoder = new TextEncoder();
const encoded = encoder.encode(str);
this.writeBytes(encoded);
}
writeUint32(value) {
this.ensureCapacity(4);
const dataView = new DataView(this.buffer);
dataView.setUint32(this.position, value, true); // little endian
this.position += 4;
}
toArrayBuffer() {
return this.buffer.slice(0, this.position);
}
reset() {
this.position = 0;
}
get size() {
return this.position;
}
get capacity() {
return this.buffer.byteLength;
}
}
// Usage
const dynBuffer = new DynamicBuffer(64, 1024);
dynBuffer.writeString('Hello ');
dynBuffer.writeString('World!');
dynBuffer.writeUint32(42);
console.log(`Used: ${dynBuffer.size}, Capacity: ${dynBuffer.capacity}`);
// SharedArrayBuffer resizable (for workers)
class SharedDynamicBuffer {
constructor(initialSize = 1024, maxSize = 1024 * 1024) {
this.sharedBuffer = new SharedArrayBuffer(initialSize, { maxByteLength: maxSize });
this.header = new Int32Array(this.sharedBuffer, 0, 2); // [position, capacity]
this.data = new Uint8Array(this.sharedBuffer, 8); // Data after header
// Initialize header
Atomics.store(this.header, 0, 0); // position
Atomics.store(this.header, 1, initialSize - 8); // capacity
}
ensureCapacity(additionalBytes) {
const currentPosition = Atomics.load(this.header, 0);
const currentCapacity = Atomics.load(this.header, 1);
const requiredSize = currentPosition + additionalBytes;
if (requiredSize > currentCapacity) {
const newSize = Math.min(
Math.max(this.sharedBuffer.byteLength * 2, requiredSize + 8),
this.sharedBuffer.maxByteLength
);
this.sharedBuffer.resize(newSize);
this.data = new Uint8Array(this.sharedBuffer, 8);
Atomics.store(this.header, 1, newSize - 8);
}
}
atomicWrite(data) {
this.ensureCapacity(data.length);
const position = Atomics.load(this.header, 0);
// Write data
this.data.set(data, position);
// Update position atomically
Atomics.store(this.header, 0, position + data.length);
return position;
}
getSharedBuffer() {
return this.sharedBuffer;
}
}
// Usage with workers
const sharedDynBuffer = new SharedDynamicBuffer(128, 2048);
// In worker
const worker = new Worker(`
const sharedBuffer = self.sharedBuffer;
const header = new Int32Array(sharedBuffer, 0, 2);
const data = new Uint8Array(sharedBuffer, 8);
self.postMessage({
position: Atomics.load(header, 0),
capacity: Atomics.load(header, 1)
});
`);Practical Applications
Combined ES2024 Features:
// Practical Example: Advanced Data Processing Pipeline
class DataProcessor {
constructor() {
this.cache = new Map();
this.processors = [];
}
// Using Array.prototype.toSorted() and with()
processArray(data, sortKey) {
return data
.toSorted((a, b) => a[sortKey] - b[sortKey]) // Non-mutating sort
.with(0, { ...data[0], processed: true }); // Mark first item
}
// Using Promise.withResolvers() for async processing
createProcessor() {
const { promise, resolve, reject } = Promise.withResolvers();
const processor = {
id: Math.random().toString(36).substr(2, 9),
promise,
resolve,
reject,
start: Date.now()
};
this.processors.push(processor);
return processor;
}
// Using RegExp v-flag for validation
validateEmailList(emails) {
const emailPattern = /^[\p{L}\p{N}._%-]+@[\p{L}\p{N}.-]+\.[\p{L}]{2,}$/v;
return emails
.filter(email => email.isWellFormed()) // Well-formed Unicode
.filter(email => emailPattern.test(email))
.toSorted(); // Sort alphabetically
}
// Comprehensive example
async processUserData(users) {
const processor = this.createProcessor();
try {
// Validate and clean user data
const validUsers = users
.filter(user => user.email.isWellFormed())
.map(user => ({
...user,
email: user.email.toWellFormed()
}));
// Sort by registration date (non-mutating)
const sortedUsers = validUsers.toSorted((a, b) =>
new Date(a.registeredAt) - new Date(b.registeredAt)
);
// Mark VIP users (non-mutating update)
const processedUsers = sortedUsers.map((user, index) => {
if (user.purchases > 1000) {
return sortedUsers.with(index, { ...user, vip: true });
}
return user;
});
processor.resolve(processedUsers);
return processor.promise;
} catch (error) {
processor.reject(error);
throw error;
}
}
}
// Modern Email Validator using ES2024 features
class EmailValidator {
constructor() {
// Using Unicode Sets (v-flag)
this.patterns = {
basic: /^[\p{L}\p{N}._%-]+@[\p{L}\p{N}.-]+\.[\p{L}]{2,}$/v,
strict: /^[\p{ASCII}&&[^\p{Cc}\p{Z}]]+@[\p{ASCII}&&[^\p{Cc}\p{Z}]]+\.[\p{L}]{2,}$/v
};
}
validateBatch(emails) {
const results = emails.map(email => ({
email: email.isWellFormed() ? email : email.toWellFormed(),
valid: false,
errors: []
}));
return results
.map((result, index) => {
const email = result.email;
if (!email.isWellFormed()) {
return results.with(index, {
...result,
errors: [...result.errors, 'Invalid Unicode sequence']
});
}
if (!this.patterns.basic.test(email)) {
return results.with(index, {
...result,
errors: [...result.errors, 'Invalid email format']
});
}
return results.with(index, { ...result, valid: true });
});
}
getValidEmails(emails) {
return this.validateBatch(emails)
.filter(result => result.valid)
.map(result => result.email)
.toSorted(); // Sort alphabetically
}
}
// Usage
const processor = new DataProcessor();
const validator = new EmailValidator();
const userData = [
{ name: 'Alice', email: 'alice@example.com', purchases: 1500, registeredAt: '2024-01-15' },
{ name: 'Bob', email: 'bob@test.com', purchases: 500, registeredAt: '2024-02-20' },
{ name: 'Charlie', email: 'charlie@demo.org', purchases: 2000, registeredAt: '2024-01-10' }
];
processor.processUserData(userData).then(users => {
console.log('Processed users:', users);
});
const emails = ['alice@example.com', 'invalid-email', 'bob@test.com\uD800'];
const validEmails = validator.getValidEmails(emails);
console.log('Valid emails:', validEmails);Performance Tips:
- Use
toSorted(),toReversed(),with()for immutable operations - RegExp v-flag offers better Unicode performance
Promise.withResolvers()avoids nested Promise constructorsisWellFormed()check before string operations- Use resizable buffers for dynamic data structures
