Modern JavaScript: Beyond the Basics
A deep dive into modern JavaScript features that go beyond tutorials—the techniques and patterns that separate intermediate from advanced developers.
Modern JavaScript: Beyond the Basics
JavaScript has evolved tremendously over the past few years. Let’s explore some advanced features and patterns that can level up your code.
Advanced Async Patterns
Promise Combinators
Beyond Promise.all, modern JavaScript offers several combinators:
// Promise.allSettled - Wait for all, regardless of success/failure
const results = await Promise.allSettled([
fetch('/api/user'),
fetch('/api/posts'),
fetch('/api/comments'),
]);
// Promise.race - First to finish wins
const fastest = await Promise.race([
fetch('/api/server1'),
fetch('/api/server2'),
]);
// Promise.any - First success wins (ignores failures)
const firstSuccess = await Promise.any([
fetchFromCDN1(),
fetchFromCDN2(),
fetchFromCDN3(),
]);
Async Iteration
async function* generateData() {
for (let i = 0; i < 5; i++) {
await delay(1000);
yield i;
}
}
// Consume with for-await-of
for await (const value of generateData()) {
console.log(value); // 0, 1, 2, 3, 4 (with 1s delays)
}
Advanced Object Patterns
Proxy for Validation
const createValidatedObject = (schema) => {
return new Proxy(
{},
{
set(target, property, value) {
const validator = schema[property];
if (!validator || validator(value)) {
target[property] = value;
return true;
}
throw new Error(`Invalid value for ${property}`);
},
}
);
};
const user = createValidatedObject({
age: (val) => typeof val === 'number' && val > 0,
email: (val) => /\S+@\S+\.\S+/.test(val),
});
user.age = 25; // OK
user.email = '[email protected]'; // OK
user.age = -5; // Throws error
Private Fields
class BankAccount {
#balance = 0; // Private field
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
}
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount();
account.deposit(100);
console.log(account.getBalance()); // 100
console.log(account.#balance); // SyntaxError
Functional Patterns
Composition and Pipe
const compose =
(...fns) =>
(x) =>
fns.reduceRight((acc, fn) => fn(acc), x);
const pipe =
(...fns) =>
(x) =>
fns.reduce((acc, fn) => fn(acc), x);
// Usage
const addOne = (x) => x + 1;
const double = (x) => x * 2;
const square = (x) => x * x;
const calculate = pipe(
addOne, // 6
double, // 12
square // 144
);
console.log(calculate(5)); // 144
Currying
const curry = (fn) => {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...nextArgs) => curried(...args, ...nextArgs);
};
};
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
Performance Optimization
Memoization
const memoize = (fn) => {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
};
const fibonacci = memoize((n) => {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
WeakMap for Private Data
const privateData = new WeakMap();
class User {
constructor(name, password) {
this.name = name;
privateData.set(this, { password });
}
verifyPassword(input) {
return privateData.get(this).password === input;
}
}
Modern Tooling Integration
Optional Chaining and Nullish Coalescing
// Optional chaining
const userName = user?.profile?.name ?? 'Guest';
// Nullish coalescing (only null/undefined, not falsy)
const count = response.count ?? 0; // 0 if null/undefined
const isActive = response.isActive ?? true; // true if null/undefined
Top-Level Await (ES2022)
// In modules, you can use await at the top level
const data = await fetch('/api/config').then((r) => r.json());
export default data;
Conclusion
Modern JavaScript is incredibly powerful. These patterns and features allow you to write more expressive, maintainable, and performant code.
The key is knowing when to use them—advanced techniques should solve problems, not create complexity for complexity’s sake.
Keep learning, keep experimenting, and most importantly, keep building!