--- name: modern-javascript-patterns description: Master ES6+ features including async/await, destructuring, spread operators, arrow functions, promises, modules, iterators, generators, and functional programming patterns for writing clean, efficient JavaScript code. Use when refactoring legacy code, implementing modern patterns, or optimizing JavaScript applications. --- # 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:** ```javascript // 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:** ```javascript 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:** ```javascript 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:** ```javascript 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:** ```javascript // 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:** ```javascript // 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 ```javascript // Basic usage const name = 'John'; const greeting = `Hello, ${name}!`; // Multi-line strings const html = `

${title}

${content}

`; // 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 + `${value}`; }, ''); } const name = 'John'; const age = 30; const html = highlight`Name: ${name}, Age: ${age}`; // Output: "Name: John, Age: 30" ``` ### 5. Enhanced Object Literals ```javascript 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:** ```javascript // 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:** ```javascript // 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:** ```javascript // 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:** ```javascript // 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:** ```javascript 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:** ```javascript // 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:** ```javascript // 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:** ```javascript // 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 ```javascript // 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 ```javascript // 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 ```javascript // 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) ```javascript // 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 ```javascript // 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 ```javascript // 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 ```javascript // 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 - **MDN Web Docs**: https://developer.mozilla.org/en-US/docs/Web/JavaScript - **JavaScript.info**: https://javascript.info/ - **You Don't Know JS**: https://github.com/getify/You-Dont-Know-JS - **Eloquent JavaScript**: https://eloquentjavascript.net/ - **ES6 Features**: http://es6-features.org/