⚡ TECH BLOG
Home
Blog
Tags
About
⚡

Powered by Next.js 15 & Modern Web Tech ⚡

Back to Home

JavaScript ES2021 Features: What's New and How to Use Them

March 15, 2021
javascriptes2021ecmascriptfrontend
JavaScript ES2021 Features: What's New and How to Use Them

JavaScript ES2021 Features: What's New and How to Use Them

ES2021 (ECMAScript 2021) brings several exciting new features to JavaScript. In this article, we'll explore each new feature with practical examples and learn how they can improve your code.

Overview of ES2021 Features

ES2021 introduces five new features:

  1. Logical Assignment Operators (??=, ||=, &&=)
  2. Promise.any() and AggregateError
  3. String.prototype.replaceAll()
  4. Numeric Separators
  5. WeakRef and FinalizationRegistry

Let's dive into each one.

1. Logical Assignment Operators

ES2021 introduces three new logical assignment operators that combine logical operations with assignment.

Nullish Coalescing Assignment (??=)

Assigns only if the current value is null or undefined:

let user = null;
user ??= 'Anonymous';
console.log(user); // 'Anonymous'

let count = 0;
count ??= 10;
console.log(count); // 0 (not assigned because 0 is not null/undefined)

// Equivalent to:
if (user === null || user === undefined) {
  user = 'Anonymous';
}

Logical OR Assignment (||=)

Assigns only if the current value is falsy:

let title = '';
title ||= 'Untitled';
console.log(title); // 'Untitled'

let count = 0;
count ||= 1;
console.log(count); // 1 (0 is falsy, so assignment happens)

let options = { debug: false };
options.debug ||= true;
console.log(options.debug); // true

// Equivalent to:
if (!title) {
  title = 'Untitled';
}

Logical AND Assignment (&&=)

Assigns only if the current value is truthy:

let value = 'hello';
value &&= value.toUpperCase();
console.log(value); // 'HELLO'

let empty = null;
empty &&= 'assigned';
console.log(empty); // null (null is falsy, no assignment)

// Useful for conditional updates
let config = { timeout: 3000 };
config.timeout &&= Math.min(config.timeout, 5000);
console.log(config.timeout); // 3000

// Equivalent to:
if (value) {
  value = value.toUpperCase();
}

Practical Example: Configuration Defaults

function setupConnection(options = {}) {
  // Set defaults for missing values
  options.host ??= 'localhost';
  options.port ??= 3000;
  options.timeout ??= 5000;
  
  // Enable debug only if not explicitly set
  options.debug ||= false;
  
  // Apply validation only if retryCount exists
  options.retryCount &&= Math.min(options.retryCount, 10);
  
  return options;
}

setupConnection({ host: 'api.example.com' });
// { host: 'api.example.com', port: 3000, timeout: 5000, debug: false }

2. Promise.any() and AggregateError

Promise.any() accepts an array of promises and resolves as soon as any of the promises fulfills.

Basic Usage

const promises = [
  fetch('/api/endpoint1'),
  fetch('/api/endpoint2'),
  fetch('/api/endpoint3')
];

Promise.any(promises)
  .then(firstResponse => {
    console.log('First successful response:', firstResponse);
  })
  .catch(error => {
    console.log('All promises rejected:', error);
  });

Comparison with Other Promise Methods

// Promise.all() - Waits for ALL to fulfill, rejects if ANY rejects
Promise.all([p1, p2, p3]);

// Promise.race() - Resolves/rejects with the FIRST settled promise
Promise.race([p1, p2, p3]);

// Promise.allSettled() - Waits for ALL to settle, never rejects
Promise.allSettled([p1, p2, p3]);

// Promise.any() - Resolves with the FIRST fulfillment, rejects if ALL reject
Promise.any([p1, p2, p3]);

AggregateError

When all promises reject, Promise.any() throws an AggregateError:

const failingPromises = [
  Promise.reject(new Error('Server 1 down')),
  Promise.reject(new Error('Server 2 down')),
  Promise.reject(new Error('Server 3 down'))
];

try {
  const result = await Promise.any(failingPromises);
} catch (error) {
  console.log(error instanceof AggregateError); // true
  console.log(error.errors); // Array of all rejection reasons
  // [Error: Server 1 down, Error: Server 2 down, Error: Server 3 down]
}

Practical Example: Multiple API Endpoints

async function fetchWithFallback(urls) {
  try {
    const response = await Promise.any(
      urls.map(url => fetch(url).then(res => {
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return res;
      }))
    );
    return await response.json();
  } catch (aggregateError) {
    console.error('All endpoints failed:', aggregateError.errors);
    throw new Error('No available server');
  }
}

// Try multiple CDN endpoints
const data = await fetchWithFallback([
  'https://cdn1.example.com/data.json',
  'https://cdn2.example.com/data.json',
  'https://cdn3.example.com/data.json'
]);

3. String.prototype.replaceAll()

Before ES2021, replacing all occurrences of a substring required using regular expressions with the global flag.

The Problem with replace()

const text = 'The quick brown fox jumps over the lazy fox.';

// replace() only replaces the first occurrence
text.replace('fox', 'cat');
// 'The quick brown cat jumps over the lazy fox.'

// Had to use regex for all occurrences
text.replace(/fox/g, 'cat');
// 'The quick brown cat jumps over the lazy cat.'

Using replaceAll()

const text = 'The quick brown fox jumps over the lazy fox.';

// Now simple and intuitive
text.replaceAll('fox', 'cat');
// 'The quick brown cat jumps over the lazy cat.'

// No more regex escaping issues
const code = 'function() { return a + b; }';
code.replaceAll('()', '[]');
// 'function[] { return a + b; }'

// With regex, you'd need to escape special characters
code.replace(/\(\)/g, '[]');

Practical Examples

// Sanitizing user input
const userInput = '<script>alert("XSS")</script>';
const sanitized = userInput
  .replaceAll('<', '&lt;')
  .replaceAll('>', '&gt;');
// '&lt;script&gt;alert("XSS")&lt;/script&gt;'

// Template processing
const template = 'Hello {name}, welcome to {place}!';
const result = template
  .replaceAll('{name}', 'John')
  .replaceAll('{place}', 'our platform');
// 'Hello John, welcome to our platform!'

// Formatting paths
const path = 'folder\\subfolder\\file.txt';
const normalized = path.replaceAll('\\', '/');
// 'folder/subfolder/file.txt'

4. Numeric Separators

Numeric separators make large numbers more readable by allowing underscores between digits.

Basic Usage

// Without separators - hard to read
const billion = 1000000000;
const bytes = 1073741824;

// With separators - much clearer
const billion = 1_000_000_000;
const bytes = 1_073_741_824;

// Works with different number formats
const decimal = 1_000.5;
const hex = 0xFF_FF_FF_FF;
const binary = 0b1010_0001_1000_0101;
const octal = 0o1234_5670;
const bigint = 1_000_000_000n;

Use Cases

// Financial values
const price = 9_999.99;
const budget = 1_500_000.00;

// File sizes
const kilobyte = 1_024;
const megabyte = 1_048_576;
const gigabyte = 1_073_741_824;

// Time constants
const millisecondsPerSecond = 1_000;
const secondsPerMinute = 60;
const secondsPerHour = 3_600;
const secondsPerDay = 86_400;

// Bit masks
const READ_PERMISSION = 0b0000_0001;
const WRITE_PERMISSION = 0b0000_0010;
const EXECUTE_PERMISSION = 0b0000_0100;

// Credit card or ID numbers (for display)
const displayNumber = '1234_5678_9012_3456';

Important Notes

// Separators are removed at runtime
console.log(1_000); // 1000
console.log(1_000 === 1000); // true

// Restrictions
// const invalid1 = _100;    // SyntaxError - can't start with _
// const invalid2 = 100_;    // SyntaxError - can't end with _
// const invalid3 = 1__000;  // SyntaxError - no consecutive underscores
// const invalid4 = 1_.0;    // SyntaxError - not next to decimal point

5. WeakRef and FinalizationRegistry

These features provide low-level capabilities for managing memory and cleanup.

WeakRef

A WeakRef holds a weak reference to an object, allowing it to be garbage collected:

let obj = { data: 'important' };
const weakRef = new WeakRef(obj);

// Access the object
console.log(weakRef.deref()); // { data: 'important' }

// After obj is no longer referenced elsewhere
obj = null;

// The object might be garbage collected
console.log(weakRef.deref()); // Could be undefined

FinalizationRegistry

Register cleanup callbacks when objects are garbage collected:

const registry = new FinalizationRegistry((id) => {
  console.log(`Object with ID ${id} was garbage collected`);
});

let obj = { data: 'some data' };
registry.register(obj, 'my-object-id');

// When obj is garbage collected, the callback runs
obj = null;
// Eventually: "Object with ID my-object-id was garbage collected"

Practical Use Case: Caching

class WeakCache {
  constructor() {
    this.cache = new Map();
    this.registry = new FinalizationRegistry(key => {
      this.cache.delete(key);
    });
  }

  get(key) {
    const weakRef = this.cache.get(key);
    return weakRef?.deref();
  }

  set(key, value) {
    this.cache.set(key, new WeakRef(value));
    this.registry.register(value, key);
  }
}

// Usage
const cache = new WeakCache();
cache.set('user-1', { name: 'John', data: new Array(1000000) });

// The cached data can be garbage collected under memory pressure
console.log(cache.get('user-1')); // { name: 'John', data: [...] }

Warning About WeakRef

The documentation explicitly warns that WeakRef should be avoided in most cases:

// AVOID - Checking if an object was collected
function checkCollected(obj) {
  const ref = new WeakRef(obj);
  return () => ref.deref() === undefined;
}

// This pattern is unreliable and discouraged

WeakRef is primarily useful for:

  • Caching large objects
  • Holding references to DOM elements
  • Implementing weak maps with custom behavior

Browser and Node.js Support

ES2021 features are widely supported:

FeatureChromeFirefoxSafariNode.js
Logical Assignment85+79+14+15+
Promise.any()85+79+14+15+
replaceAll()85+77+13.1+15+
Numeric Separators75+70+13+12.5+
WeakRef84+79+14+14+

Conclusion

ES2021 brings practical features that make JavaScript more expressive and convenient:

  • Logical Assignment Operators simplify common assignment patterns
  • Promise.any() provides a useful alternative to Promise.race()
  • replaceAll() makes string replacement more intuitive
  • Numeric Separators improve code readability
  • WeakRef enables advanced memory management patterns

Start using these features today to write cleaner, more maintainable JavaScript code!

Further Resources

  • ECMAScript 2021 Specification
  • MDN Web Docs - ES2021
  • V8 Blog - ES2021 Features
Share:

💬 Comments