| 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:
function add(a, b) {
return a + b
}
const add = (a, b) => a + b
const double = x => x * 2
const getRandom = () => Math.random()
const processUser = user => {
const normalized = user.name.toLowerCase()
return {...user, name: normalized}
}
const createUser = (name, age) => ({name, age})
Lexical 'this' Binding:
class Counter {
constructor() {
this.count = 0
}
increment = () => {
this.count++
}
incrementTraditional() {
setTimeout(function () {
this.count++
}, 1000)
}
incrementArrow() {
setTimeout(() => {
this.count++
}, 1000)
}
}
2. Destructuring
Object Destructuring:
const user = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
address: {
city: 'New York',
country: 'USA',
},
}
const {name, email} = user
const {name: userName, email: userEmail} = user
const {age = 25} = user
const {
address: {city, country},
} = user
const {id, ...userWithoutId} = user
function greet({name, age = 18}) {
console.log(`Hello ${name}, you are ${age}`)
}
greet(user)
Array Destructuring:
const numbers = [1, 2, 3, 4, 5]
const [first, second] = numbers
const [, , third] = numbers
const [head, ...tail] = numbers
let a = 1,
b = 2
;[a, b] = [b, a]
function getCoordinates() {
return [10, 20]
}
const [x, y] = getCoordinates()
const [one, two, three = 0] = [1, 2]
3. Spread and Rest Operators
Spread Operator:
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const combined = [...arr1, ...arr2]
const defaults = {theme: 'dark', lang: 'en'}
const userPrefs = {theme: 'light'}
const settings = {...defaults, ...userPrefs}
const numbers = [1, 2, 3]
Math.max(...numbers)
const copy = [...arr1]
const objCopy = {...user}
const newArr = [...arr1, 4, 5]
const newObj = {...user, age: 30}
Rest Parameters:
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0)
}
sum(1, 2, 3, 4, 5)
function greet(greeting, ...names) {
return `${greeting} ${names.join(', ')}`
}
greet('Hello', 'John', 'Jane', 'Bob')
const {id, ...userData} = user
const [first, ...rest] = [1, 2, 3, 4, 5]
4. Template Literals
const name = 'John'
const greeting = `Hello, ${name}!`
const html = `
<div>
<h1>${title}</h1>
<p>${content}</p>
</div>
`
const price = 19.99
const total = `Total: $${(price * 1.2).toFixed(2)}`
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}`
5. Enhanced Object Literals
const name = 'John'
const age = 30
const user = {name, age}
const calculator = {
add(a, b) {
return a + b
},
subtract(a, b) {
return a - b
},
}
const field = 'email'
const user = {
name: 'John',
[field]: 'john@example.com',
[`get${field.charAt(0).toUpperCase()}${field.slice(1)}`]() {
return this[field]
},
}
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:
const fetchUser = id => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({id, name: 'John'})
} else {
reject(new Error('Invalid ID'))
}
}, 1000)
})
}
fetchUser(1)
.then(user => console.log(user))
.catch(error => console.error(error))
.finally(() => console.log('Done'))
fetchUser(1)
.then(user => fetchUserPosts(user.id))
.then(posts => processPosts(posts))
.then( => .(result))
.( .(error))
Promise Combinators:
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(promises).then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value)
} else {
console.log('Error:', result.reason)
}
})
})
Promise.race(promises)
.then(winner => console.log(, winner))
.( .(error))
.(promises)
.( .(, first))
.( .(, error))
2. Async/Await
Basic Usage:
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`)
const user = await response.json()
return user
}
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
}
}
async function sequential() {
const user1 = await fetchUser(1)
const user2 = await fetchUser(2)
return [user1, user2]
}
async function () {
[user1, user2] = .([(), ()])
[user1, user2]
}
Advanced Patterns:
;(async () => {
const result = await someAsyncOperation()
console.log(result)
})()
async function processUsers(userIds) {
for (const id of userIds) {
const user = await fetchUser(id)
await processUser(user)
}
}
const config = await fetch('/config.json').then(r => r.json())
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, * (i + )))
}
}
}
() {
timeout = (
( ( ()), ms),
)
.([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},
]
const names = users.map(user => user.name)
const upperNames = users.map(user => user.name.toUpperCase())
const activeUsers = users.filter(user => user.active)
const adults = users.filter(user => user.age >= 18)
const totalAge = users.reduce((sum, user) => sum + user.age, )
avgAge = totalAge / users.
byActive = users.( {
key = user. ? :
{
...groups,
[key]: [...(groups[key] || []), user],
}
}, {})
result = users
.( user.)
.( user.)
.()
.()
Advanced Array Methods:
const user = users.find(u => u.id === 2)
const index = users.findIndex(u => u.name === 'Jane')
const hasActive = users.some(u => u.active)
const allAdults = users.every(u => u.age >= 18)
const userTags = [
{name: 'John', tags: ['admin', 'user']},
{name: 'Jane', tags: ['user']},
]
const allTags = userTags.flatMap(u => u.tags)
const str = 'hello'
const chars = Array.from(str)
const numbers = Array.from({length: }, i + )
arr = .(, , )
2. Higher-Order Functions
Functions as Arguments:
function forEach(array, callback) {
for (let i = 0; i < array.length; i++) {
callback(array[i], i, array)
}
}
function map(array, transform) {
const result = []
for (const item of array) {
result.push(transform(item))
}
return result
}
function filter(array, predicate) {
const result = []
for (const item of array) {
if (predicate(item)) {
result.push(item)
}
}
return result
}
Functions Returning Functions:
const multiply = a => b => a * b
const double = multiply(2)
const triple = multiply(3)
console.log(double(5))
console.log(triple(5))
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))
function memoize(fn) {
const cache = new Map()
return (...args) => {
const key = JSON.(args)
(cache.(key)) {
cache.(key)
}
result = (...args)
cache.(key, result)
result
}
}
fibonacci = ( {
(n <= ) n
(n - ) + (n - )
})
3. Composition and Piping
const compose =
(...fns) =>
x =>
fns.reduceRight((acc, fn) => fn(acc), x)
const pipe =
(...fns) =>
x =>
fns.reduce((acc, fn) => fn(acc), x)
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))
const piped = pipe(addOne, double, square)
console.log(piped(3))
const processUser = pipe(
user => ({...user, name: user.name.trim()}),
user => ({...user, : user..()}),
({...user, : (user.)}),
)
user = ({
: ,
: ,
: ,
})
4. Pure Functions and Immutability
function addItemImpure(cart, item) {
cart.items.push(item)
cart.total += item.price
return cart
}
function addItemPure(cart, item) {
return {
...cart,
items: [...cart.items, item],
total: cart.total + item.price,
}
}
const numbers = [1, 2, 3, 4, 5]
const withSix = [...numbers, 6]
const withoutThree = numbers.filter(n => n !== 3)
const doubled = numbers.map(n => (n === 3 ? n * 2 : n))
const user = {name: 'John', age: 30}
const olderUser = {...user, age: }
withEmail = {...user, : }
{age, ...withoutAge} = user
= obj => .(.(obj))
= obj => globalThis.(obj)
Modern Class Features
class User {
#password
id
name
static count = 0
constructor(id, name, password) {
this.id = id
this.name = name
this.#password = password
User.count++
}
greet() {
return `Hello, ${this.name}`
}
#hashPassword(password) {
return `hashed_${password}`
}
get displayName() {
return this.name.toUpperCase()
}
set password(newPassword) {
this.#password = this.#hashPassword(newPassword)
}
static create(id, name, password) {
return new (id, name, password)
}
}
{
() {
(id, name, password)
. = role
}
() {
}
}
Modules (ES6)
export const PI = 3.14159
export function add(a, b) {
return a + b
}
export class Calculator {
}
export default function multiply(a, b) {
return a * b
}
import multiply, {PI, add, Calculator} from './math.js'
import {add as sum} from './math.js'
import * as Math from './math.js'
const module = await import('./math.js')
const {add} = await import('./math.js')
if (condition) {
const = ()
.()
}
Iterators and Generators
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)
}
function* rangeGenerator(from, to) {
for (let i = from; i <= to; i++) {
yield i
}
}
for (const num of rangeGenerator(1, 5)) {
console.(num)
}
* () {
[prev, curr] = [, ]
() {
curr
;[prev, curr] = [curr, prev + curr]
}
}
* () {
page =
() {
response = ()
data = response.()
(data. === )
data
page++
}
}
( page ()) {
.(page)
}
Modern Operators
const user = {name: 'John', address: {city: 'NYC'}}
const city = user?.address?.city
const zipCode = user?.address?.zipCode
const result = obj.method?.()
const first = arr?.[0]
const value = null ?? 'default'
const value = undefined ?? 'default'
const value = 0 ?? 'default'
const value = '' ?? 'default'
let a = null
a ??= 'default'
let b = 5
b ??= 10
let obj = {count: 0}
obj.count ||= 1
obj.count &&=
Performance Optimization
function debounce(fn, delay) {
let timeoutId
return (...args) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => fn(...args), delay)
}
}
const searchDebounced = debounce(search, 300)
function throttle(fn, limit) {
let inThrottle
return (...args) => {
if (!inThrottle) {
fn(...args)
inThrottle = true
setTimeout(() => (inThrottle = false), limit)
}
}
}
const scrollThrottled = throttle(handleScroll, 100)
function* lazyMap(iterable, transform) {
for (const item of iterable) {
yield transform(item)
}
}
const numbers = [1, 2, 3, 4, 5]
const doubled = lazyMap(numbers, => x * )
first = doubled.().
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