modern-javascript-patterns

Installation
SKILL.md

Modern JavaScript Patterns

Comprehensive guide for mastering modern JavaScript (ES6+) features, functional programming patterns, and best practices for writing clean, maintainable, and performant code.

When to Use This Skill

  • Refactoring legacy JavaScript to modern syntax
  • Implementing functional programming patterns
  • Optimizing JavaScript performance
  • Writing maintainable and readable code
  • Working with asynchronous operations
  • Building modern web applications
  • Migrating from callbacks to Promises/async-await
  • Implementing data transformation pipelines

ES6+ Core Features

1. Arrow Functions

Syntax and Use Cases:

// Traditional function
function add(a, b) {
	return a + b;
}

// Arrow function
const add = (a, b) => a + b;

// Single parameter (parentheses optional)
const double = x => x * 2;

// No parameters
const getRandom = () => Math.random();

// Multiple statements (need curly braces)
const processUser = user => {
	const normalized = user.name.toLowerCase();
	return {...user, name: normalized};
};

// Returning objects (wrap in parentheses)
const createUser = (name, age) => ({name, age});

Lexical 'this' Binding:

class Counter {
	constructor() {
		this.count = 0;
	}

	// Arrow function preserves 'this' context
	increment = () => {
		this.count++;
	};

	// Traditional function loses 'this' in callbacks
	incrementTraditional() {
		setTimeout(function () {
			this.count++; // 'this' is undefined
		}, 1000);
	}

	// Arrow function maintains 'this'
	incrementArrow() {
		setTimeout(() => {
			this.count++; // 'this' refers to Counter instance
		}, 1000);
	}
}

2. Destructuring

Object Destructuring:

const user = {
	id: 1,
	name: 'John Doe',
	email: 'john@example.com',
	address: {
		city: 'New York',
		country: 'USA',
	},
};

// Basic destructuring
const {name, email} = user;

// Rename variables
const {name: userName, email: userEmail} = user;

// Default values
const {age = 25} = user;

// Nested destructuring
const {
	address: {city, country},
} = user;

// Rest operator
const {id, ...userWithoutId} = user;

// Function parameters
function greet({name, age = 18}) {
	console.log(`Hello ${name}, you are ${age}`);
}
greet(user);

Array Destructuring:

const numbers = [1, 2, 3, 4, 5];

// Basic destructuring
const [first, second] = numbers;

// Skip elements
const [, , third] = numbers;

// Rest operator
const [head, ...tail] = numbers;

// Swapping variables
let a = 1,
	b = 2;
[a, b] = [b, a];

// Function return values
function getCoordinates() {
	return [10, 20];
}
const [x, y] = getCoordinates();

// Default values
const [one, two, three = 0] = [1, 2];

3. Spread and Rest Operators

Spread Operator:

// Array spreading
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];

// Object spreading
const defaults = {theme: 'dark', lang: 'en'};
const userPrefs = {theme: 'light'};
const settings = {...defaults, ...userPrefs};

// Function arguments
const numbers = [1, 2, 3];
Math.max(...numbers);

// Copying arrays/objects (shallow copy)
const copy = [...arr1];
const objCopy = {...user};

// Adding items immutably
const newArr = [...arr1, 4, 5];
const newObj = {...user, age: 30};

Rest Parameters:

// Collect function arguments
function sum(...numbers) {
	return numbers.reduce((total, num) => total + num, 0);
}
sum(1, 2, 3, 4, 5);

// With regular parameters
function greet(greeting, ...names) {
	return `${greeting} ${names.join(', ')}`;
}
greet('Hello', 'John', 'Jane', 'Bob');

// Object rest
const {id, ...userData} = user;

// Array rest
const [first, ...rest] = [1, 2, 3, 4, 5];

4. Template Literals

// Basic usage
const name = 'John';
const greeting = `Hello, ${name}!`;

// Multi-line strings
const html = `
  <div>
    <h1>${title}</h1>
    <p>${content}</p>
  </div>
`;

// Expression evaluation
const price = 19.99;
const total = `Total: $${(price * 1.2).toFixed(2)}`;

// Tagged template literals
function highlight(strings, ...values) {
	return strings.reduce((result, str, i) => {
		const value = values[i] || '';
		return result + str + `<mark>${value}</mark>`;
	}, '');
}

const name = 'John';
const age = 30;
const html = highlight`Name: ${name}, Age: ${age}`;
// Output: "Name: <mark>John</mark>, Age: <mark>30</mark>"

5. Enhanced Object Literals

const name = 'John';
const age = 30;

// Shorthand property names
const user = {name, age};

// Shorthand method names
const calculator = {
	add(a, b) {
		return a + b;
	},
	subtract(a, b) {
		return a - b;
	},
};

// Computed property names
const field = 'email';
const user = {
	name: 'John',
	[field]: 'john@example.com',
	[`get${field.charAt(0).toUpperCase()}${field.slice(1)}`]() {
		return this[field];
	},
};

// Dynamic property creation
const createUser = (name, ...props) => {
	return props.reduce(
		(user, [key, value]) => ({
			...user,
			[key]: value,
		}),
		{name},
	);
};

const user = createUser('John', ['age', 30], ['email', 'john@example.com']);

Asynchronous Patterns

1. Promises

Creating and Using Promises:

// Creating a promise
const fetchUser = id => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			if (id > 0) {
				resolve({id, name: 'John'});
			} else {
				reject(new Error('Invalid ID'));
			}
		}, 1000);
	});
};

// Using promises
fetchUser(1)
	.then(user => console.log(user))
	.catch(error => console.error(error))
	.finally(() => console.log('Done'));

// Chaining promises
fetchUser(1)
	.then(user => fetchUserPosts(user.id))
	.then(posts => processPosts(posts))
	.then(result => console.log(result))
	.catch(error => console.error(error));

Promise Combinators:

// Promise.all - Wait for all promises
const promises = [fetchUser(1), fetchUser(2), fetchUser(3)];

Promise.all(promises)
	.then(users => console.log(users))
	.catch(error => console.error('At least one failed:', error));

// Promise.allSettled - Wait for all, regardless of outcome
Promise.allSettled(promises).then(results => {
	results.forEach(result => {
		if (result.status === 'fulfilled') {
			console.log('Success:', result.value);
		} else {
			console.log('Error:', result.reason);
		}
	});
});

// Promise.race - First to complete
Promise.race(promises)
	.then(winner => console.log('First:', winner))
	.catch(error => console.error(error));

// Promise.any - First to succeed
Promise.any(promises)
	.then(first => console.log('First success:', first))
	.catch(error => console.error('All failed:', error));

2. Async/Await

Basic Usage:

// Async function always returns a Promise
async function fetchUser(id) {
	const response = await fetch(`/api/users/${id}`);
	const user = await response.json();
	return user;
}

// Error handling with try/catch
async function getUserData(id) {
	try {
		const user = await fetchUser(id);
		const posts = await fetchUserPosts(user.id);
		return {user, posts};
	} catch (error) {
		console.error('Error fetching data:', error);
		throw error;
	}
}

// Sequential vs Parallel execution
async function sequential() {
	const user1 = await fetchUser(1); // Wait
	const user2 = await fetchUser(2); // Then wait
	return [user1, user2];
}

async function parallel() {
	const [user1, user2] = await Promise.all([fetchUser(1), fetchUser(2)]);
	return [user1, user2];
}

Advanced Patterns:

// Async IIFE
(async () => {
	const result = await someAsyncOperation();
	console.log(result);
})();

// Async iteration
async function processUsers(userIds) {
	for (const id of userIds) {
		const user = await fetchUser(id);
		await processUser(user);
	}
}

// Top-level await (ES2022)
const config = await fetch('/config.json').then(r => r.json());

// Retry logic
async function fetchWithRetry(url, retries = 3) {
	for (let i = 0; i < retries; i++) {
		try {
			return await fetch(url);
		} catch (error) {
			if (i === retries - 1) throw error;
			await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
		}
	}
}

// Timeout wrapper
async function withTimeout(promise, ms) {
	const timeout = new Promise((_, reject) =>
		setTimeout(() => reject(new Error('Timeout')), ms),
	);
	return Promise.race([promise, timeout]);
}

Functional Programming Patterns

1. Array Methods

Map, Filter, Reduce:

const users = [
	{id: 1, name: 'John', age: 30, active: true},
	{id: 2, name: 'Jane', age: 25, active: false},
	{id: 3, name: 'Bob', age: 35, active: true},
];

// Map - Transform array
const names = users.map(user => user.name);
const upperNames = users.map(user => user.name.toUpperCase());

// Filter - Select elements
const activeUsers = users.filter(user => user.active);
const adults = users.filter(user => user.age >= 18);

// Reduce - Aggregate data
const totalAge = users.reduce((sum, user) => sum + user.age, 0);
const avgAge = totalAge / users.length;

// Group by property
const byActive = users.reduce((groups, user) => {
	const key = user.active ? 'active' : 'inactive';
	return {
		...groups,
		[key]: [...(groups[key] || []), user],
	};
}, {});

// Chaining methods
const result = users
	.filter(user => user.active)
	.map(user => user.name)
	.sort()
	.join(', ');

Advanced Array Methods:

// Find - First matching element
const user = users.find(u => u.id === 2);

// FindIndex - Index of first match
const index = users.findIndex(u => u.name === 'Jane');

// Some - At least one matches
const hasActive = users.some(u => u.active);

// Every - All match
const allAdults = users.every(u => u.age >= 18);

// FlatMap - Map and flatten
const userTags = [
	{name: 'John', tags: ['admin', 'user']},
	{name: 'Jane', tags: ['user']},
];
const allTags = userTags.flatMap(u => u.tags);

// From - Create array from iterable
const str = 'hello';
const chars = Array.from(str);
const numbers = Array.from({length: 5}, (_, i) => i + 1);

// Of - Create array from arguments
const arr = Array.of(1, 2, 3);

2. Higher-Order Functions

Functions as Arguments:

// Custom forEach
function forEach(array, callback) {
	for (let i = 0; i < array.length; i++) {
		callback(array[i], i, array);
	}
}

// Custom map
function map(array, transform) {
	const result = [];
	for (const item of array) {
		result.push(transform(item));
	}
	return result;
}

// Custom filter
function filter(array, predicate) {
	const result = [];
	for (const item of array) {
		if (predicate(item)) {
			result.push(item);
		}
	}
	return result;
}

Functions Returning Functions:

// Currying
const multiply = a => b => a * b;
const double = multiply(2);
const triple = multiply(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

// Partial application
function partial(fn, ...args) {
	return (...moreArgs) => fn(...args, ...moreArgs);
}

const add = (a, b, c) => a + b + c;
const add5 = partial(add, 5);
console.log(add5(3, 2)); // 10

// Memoization
function 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);
});

3. Composition and Piping

// Function composition
const compose =
	(...fns) =>
	x =>
		fns.reduceRight((acc, fn) => fn(acc), x);

const pipe =
	(...fns) =>
	x =>
		fns.reduce((acc, fn) => fn(acc), x);

// Example usage
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;

const composed = compose(square, double, addOne);
console.log(composed(3)); // ((3 + 1) * 2)^2 = 64

const piped = pipe(addOne, double, square);
console.log(piped(3)); // ((3 + 1) * 2)^2 = 64

// Practical example
const processUser = pipe(
	user => ({...user, name: user.name.trim()}),
	user => ({...user, email: user.email.toLowerCase()}),
	user => ({...user, age: parseInt(user.age)}),
);

const user = processUser({
	name: '  John  ',
	email: 'JOHN@EXAMPLE.COM',
	age: '30',
});

4. Pure Functions and Immutability

// Impure function (modifies input)
function addItemImpure(cart, item) {
	cart.items.push(item);
	cart.total += item.price;
	return cart;
}

// Pure function (no side effects)
function addItemPure(cart, item) {
	return {
		...cart,
		items: [...cart.items, item],
		total: cart.total + item.price,
	};
}

// Immutable array operations
const numbers = [1, 2, 3, 4, 5];

// Add to array
const withSix = [...numbers, 6];

// Remove from array
const withoutThree = numbers.filter(n => n !== 3);

// Update array element
const doubled = numbers.map(n => (n === 3 ? n * 2 : n));

// Immutable object operations
const user = {name: 'John', age: 30};

// Update property
const olderUser = {...user, age: 31};

// Add property
const withEmail = {...user, email: 'john@example.com'};

// Remove property
const {age, ...withoutAge} = user;

// Deep cloning (simple approach)
const deepClone = obj => JSON.parse(JSON.stringify(obj));

// Better deep cloning
const structuredClone = obj => globalThis.structuredClone(obj);

Modern Class Features

// Class syntax
class User {
	// Private fields
	#password;

	// Public fields
	id;
	name;

	// Static field
	static count = 0;

	constructor(id, name, password) {
		this.id = id;
		this.name = name;
		this.#password = password;
		User.count++;
	}

	// Public method
	greet() {
		return `Hello, ${this.name}`;
	}

	// Private method
	#hashPassword(password) {
		return `hashed_${password}`;
	}

	// Getter
	get displayName() {
		return this.name.toUpperCase();
	}

	// Setter
	set password(newPassword) {
		this.#password = this.#hashPassword(newPassword);
	}

	// Static method
	static create(id, name, password) {
		return new User(id, name, password);
	}
}

// Inheritance
class Admin extends User {
	constructor(id, name, password, role) {
		super(id, name, password);
		this.role = role;
	}

	greet() {
		return `${super.greet()}, I'm an admin`;
	}
}

Modules (ES6)

// Exporting
// math.js
export const PI = 3.14159;
export function add(a, b) {
	return a + b;
}
export class Calculator {
	// ...
}

// Default export
export default function multiply(a, b) {
	return a * b;
}

// Importing
// app.js
import multiply, {PI, add, Calculator} from './math.js';

// Rename imports
import {add as sum} from './math.js';

// Import all
import * as Math from './math.js';

// Dynamic imports
const module = await import('./math.js');
const {add} = await import('./math.js');

// Conditional loading
if (condition) {
	const module = await import('./feature.js');
	module.init();
}

Iterators and Generators

// Custom iterator
const range = {
	from: 1,
	to: 5,

	[Symbol.iterator]() {
		return {
			current: this.from,
			last: this.to,

			next() {
				if (this.current <= this.last) {
					return {done: false, value: this.current++};
				} else {
					return {done: true};
				}
			},
		};
	},
};

for (const num of range) {
	console.log(num); // 1, 2, 3, 4, 5
}

// Generator function
function* rangeGenerator(from, to) {
	for (let i = from; i <= to; i++) {
		yield i;
	}
}

for (const num of rangeGenerator(1, 5)) {
	console.log(num);
}

// Infinite generator
function* fibonacci() {
	let [prev, curr] = [0, 1];
	while (true) {
		yield curr;
		[prev, curr] = [curr, prev + curr];
	}
}

// Async generator
async function* fetchPages(url) {
	let page = 1;
	while (true) {
		const response = await fetch(`${url}?page=${page}`);
		const data = await response.json();
		if (data.length === 0) break;
		yield data;
		page++;
	}
}

for await (const page of fetchPages('/api/users')) {
	console.log(page);
}

Modern Operators

// Optional chaining
const user = {name: 'John', address: {city: 'NYC'}};
const city = user?.address?.city;
const zipCode = user?.address?.zipCode; // undefined

// Function call
const result = obj.method?.();

// Array access
const first = arr?.[0];

// Nullish coalescing
const value = null ?? 'default'; // 'default'
const value = undefined ?? 'default'; // 'default'
const value = 0 ?? 'default'; // 0 (not 'default')
const value = '' ?? 'default'; // '' (not 'default')

// Logical assignment
let a = null;
a ??= 'default'; // a = 'default'

let b = 5;
b ??= 10; // b = 5 (unchanged)

let obj = {count: 0};
obj.count ||= 1; // obj.count = 1
obj.count &&= 2; // obj.count = 2

Performance Optimization

// Debounce
function debounce(fn, delay) {
	let timeoutId;
	return (...args) => {
		clearTimeout(timeoutId);
		timeoutId = setTimeout(() => fn(...args), delay);
	};
}

const searchDebounced = debounce(search, 300);

// Throttle
function throttle(fn, limit) {
	let inThrottle;
	return (...args) => {
		if (!inThrottle) {
			fn(...args);
			inThrottle = true;
			setTimeout(() => (inThrottle = false), limit);
		}
	};
}

const scrollThrottled = throttle(handleScroll, 100);

// Lazy evaluation
function* lazyMap(iterable, transform) {
	for (const item of iterable) {
		yield transform(item);
	}
}

// Use only what you need
const numbers = [1, 2, 3, 4, 5];
const doubled = lazyMap(numbers, x => x * 2);
const first = doubled.next().value; // Only computes first value

Best Practices

  1. Use const by default: Only use let when reassignment is needed
  2. Prefer arrow functions: Especially for callbacks
  3. Use template literals: Instead of string concatenation
  4. Destructure objects and arrays: For cleaner code
  5. Use async/await: Instead of Promise chains
  6. Avoid mutating data: Use spread operator and array methods
  7. Use optional chaining: Prevent "Cannot read property of undefined"
  8. Use nullish coalescing: For default values
  9. Prefer array methods: Over traditional loops
  10. Use modules: For better code organization
  11. Write pure functions: Easier to test and reason about
  12. Use meaningful variable names: Self-documenting code
  13. Keep functions small: Single responsibility principle
  14. Handle errors properly: Use try/catch with async/await
  15. Use strict mode: 'use strict' for better error catching

Common Pitfalls

  1. this binding confusion: Use arrow functions or bind()
  2. Async/await without error handling: Always use try/catch
  3. Promise creation unnecessary: Don't wrap already async functions
  4. Mutation of objects: Use spread operator or Object.assign()
  5. Forgetting await: Async functions return promises
  6. Blocking event loop: Avoid synchronous operations
  7. Memory leaks: Clean up event listeners and timers
  8. Not handling promise rejections: Use catch() or try/catch

Resources

Related skills
Installs
6
Repository
involvex/skills
First Seen
Apr 16, 2026