16 Mind-Blowing Modern JavaScript Features You Must Know

16 Mind-Blowing Modern JavaScript Features You Must Know

Dev.to
#javascript #es2022 #web-development #frontend #nodejs

This article was inspired by a trending topic from Dev.to

View original discussion

16 Modern JavaScript Features That Might Blow Your Mind

Quick take


Async and Top‑Level Improvements

Top‑Level await

Before ES2022 you needed an IIFE or an async wrapper to await something at the module root. Now you can write:

// config.js
const config = await fetch('/config.json');
export default config;

That single line replaces a dozen lines of boilerplate. Use it sparingly—only in entry‑point modules—so the startup flow stays obvious.

Promise.withResolvers()

Creating a promise with external resolve/reject used to look like:

let resolve;
const p = new Promise(r => resolve = r);

Promise.withResolvers() bundles the promise and its controls:

const { promise, resolve, reject } = Promise.withResolvers();

Perfect for custom queues, event emitters, or any situation where you need to hand a promise off to another component without nesting.

Promise.try()

Mixing sync and async error handling always felt clunky. Promise.try() normalises the flow:

await Promise.try(() => JSON.parse(maybeInvalidJson));

If the callback throws, the promise rejects automatically, letting you handle both sync and async errors in one catch.

Error.cause

Debugging multi‑step failures becomes painless:

try {
  await db.query('SELECT * FROM users');
} catch (orig) {
  throw new Error('User fetch failed', { cause: orig });
}

The original error is preserved, so stack traces show the full chain—no more “lost in translation” debugging sessions.


Immutable Array Helpers

Mutating arrays is the silent killer of predictable state, especially in Redux‑style stores. ES2023 introduced three non‑mutating counterparts:

MethodWhat it doesExample
toSorted()Returns a sorted copyconst sorted = arr.toSorted((a,b)=>a-b);
toReversed()Returns a reversed copyconst rev = arr.toReversed();
toSpliced(start, deleteCount, ...items)Returns a spliced copyconst newArr = arr.toSpliced(2,1,'x');

No more [...] cloning or Array.from() gymnastics. Your original arrays stay pristine, which means fewer “why‑is‑my‑state‑mutated?” headaches.

.at() – Relative Indexing

Getting the last element used to be arr[arr.length - 1]. Now it’s simply:

const last = arr.at(-1);

It works on strings too, so 'hello'.at(-1) yields 'o'. Readability win, zero cognitive load.

findLast() / findLastIndex()

Finding the last match previously required a reverse copy. The new methods do it in one line:

const lastEven = nums.findLast(n => n % 2 === 0);

Less noise, clearer intent.


Collection & Object Utilities

Object.hasOwn()

The classic Object.prototype.hasOwnProperty.call(obj, 'key') is now a tidy one‑liner:

if (Object.hasOwn(user, 'email')) { … }

It sidesteps prototype‑pollution pitfalls and reads like plain English.

Object.groupBy()

Grouping arrays used to be a reduce nightmare. ES2024 lets you write:

const byRole = Object.groupBy(users, u => u.role);

The result is a plain object where each key holds an array of matching items. Instant readability.

RegExp.escape()

Never again hand‑craft a regex‑escaping function. Safe regex construction is now:

const safe = new RegExp(RegExp.escape(userInput));

Helps prevent injection bugs with minimal code.

Resizable ArrayBuffer

For streaming or binary workloads, fixed‑size buffers were a pain. The new constructor accepts a maxByteLength:

const buf = new ArrayBuffer(8, { maxByteLength: 16 });

You can now grow the buffer up to the limit, which is a boon for WebGPU and real‑time audio processing.

Float16Array

Memory‑heavy numeric workloads finally get a 16‑bit float view:

const halfPrecision = new Float16Array(1024);

Half the memory footprint translates to faster data transfer on GPUs and smoother WebML pipelines.

New Set Prototype Methods

Set operations now have first‑class methods:

const a = new Set([1,2,3]);
const b = new Set([3,4,5]);

const union = a.union(b);          // Set {1,2,3,4,5}
const intersection = a.intersection(b); // Set {3}
const diff = a.difference(b);     // Set {1,2}

No more converting to arrays or writing custom helpers.


Pitfalls & Best Practices


Real‑World Use Cases

  1. Server‑Side Rendering (SSR) Config Loader – Top‑level await fetches environment variables before the render pipeline starts, eliminating an async bootstrap file.
  2. Redux Toolkit State UpdatestoSpliced() creates new arrays for list updates without the “spread‑operator fatigue” of [...arr.slice(0,i), newItem, ...arr.slice(i+1)].
  3. WebGPU Texture Streaming – Resizable ArrayBuffer lets you allocate a modest buffer upfront and grow it as textures stream in, keeping memory usage tight.
  4. Form Validation LibraryRegExp.escape() sanitises user‑provided patterns on the fly, preventing regex injection attacks in a multi‑tenant SaaS product.
  5. Analytics DashboardObject.groupBy() aggregates events by type in a single line, cutting down on boilerplate reducers.


Frequently Asked Questions

Q: Are these features safe to use in production today?
A: All listed features are ship‑ready in the latest Chrome, Edge, Firefox, and Node 20+. Always check your target browsers with tools like caniuse.com and consider a small polyfill for older environments.

Q: Do immutable array helpers impact performance?
A: They create a shallow copy, which is cheap for small to medium arrays. For massive datasets, benchmark the copy cost versus in‑place mutation; the trade‑off is usually worth the predictability.

Q: How does Error.cause differ from manually attaching a cause property?
A: The native property integrates with stack traces and Error.prototype.cause getters, giving you a standardized way to access the original error without custom plumbing.

Q: Can I use Set.union on older browsers?
A: Not directly. A simple polyfill like Set.prototype.union = function(other){ return new Set([...this, ...other]); } works, but be aware it adds a method to the prototype, which may conflict with future specs.

Q: Does Promise.try() swallow synchronous errors?
A: No. It wraps the callback in a promise, so any thrown error becomes a rejected promise, preserving the error for downstream catch blocks.


Embracing these 16 modern JavaScript features isn’t about chasing hype; it’s about writing code that’s cleaner, safer, and easier to maintain. Sprinkle them into your next project, and you’ll wonder how you ever lived without them. Happy coding!

Share this article