Source: wshobson/agents Original Plugin: javascript-typescript
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
- Use const by default: Only use let when reassignment is needed
- Prefer arrow functions: Especially for callbacks
- Use template literals: Instead of string concatenation
- Destructure objects and arrays: For cleaner code
- Use async/await: Instead of Promise chains
- Avoid mutating data: Use spread operator and array methods
- Use optional chaining: Prevent "Cannot read property of undefined"
- Use nullish coalescing: For default values
- Prefer array methods: Over traditional loops
- Use modules: For better code organization
- Write pure functions: Easier to test and reason about
- Use meaningful variable names: Self-documenting code
- Keep functions small: Single responsibility principle
- Handle errors properly: Use try/catch with async/await
- Use strict mode: 'use strict'for better error catching
Common Pitfalls
- this binding confusion: Use arrow functions or bind()
- Async/await without error handling: Always use try/catch
- Promise creation unnecessary: Don't wrap already async functions
- Mutation of objects: Use spread operator or Object.assign()
- Forgetting await: Async functions return promises
- Blocking event loop: Avoid synchronous operations
- Memory leaks: Clean up event listeners and timers
- 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/