JavaScript Patterns
Write modern JavaScript code following ES6+ patterns and best practices.
JavaScript Patterns
Guidelines for writing modern, maintainable JavaScript code.
When to Activate
- Writing or editing JavaScript code (
.js,.mjsfiles) - Creating Node.js applications
- Reviewing JavaScript code
Modern Syntax
Use const/let, never var
// GOOD
const MAX_RETRIES = 3;
let attempts = 0;
// BAD
var MAX_RETRIES = 3;
var attempts = 0;
Use arrow functions for callbacks
// GOOD
const squares = numbers.map(n => n ** 2);
const adults = users.filter(u => u.age >= 18);
// BAD
const squares = numbers.map(function(n) {
return n ** 2;
});
Use destructuring
// GOOD - object destructuring
const { name, email, role = 'user' } = user;
// GOOD - array destructuring
const [first, second, ...rest] = items;
// GOOD - parameter destructuring
function createUser({ name, email, admin = false }) {
return { name, email, admin };
}
Use template literals
// GOOD
const message = `Hello, ${user.name}! You have ${count} notifications.`;
const html = `
<div class="card">
<h2>${title}</h2>
<p>${description}</p>
</div>
`;
// BAD
const message = 'Hello, ' + user.name + '! You have ' + count + ' notifications.';
Use spread operator
// GOOD - arrays
const combined = [...arr1, ...arr2];
const copy = [...original];
// GOOD - objects
const updated = { ...user, name: 'New Name' };
const merged = { ...defaults, ...options };
Functions
Use default parameters
// GOOD
function createUser(name, role = 'user', active = true) {
return { name, role, active };
}
// BAD
function createUser(name, role, active) {
role = role || 'user';
active = active !== undefined ? active : true;
return { name, role, active };
}
Use rest parameters
// GOOD
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
// BAD
function sum() {
return Array.from(arguments).reduce((a, b) => a + b, 0);
}
Async/Await
Prefer async/await over promises
// GOOD
async function fetchUserData(userId) {
try {
const user = await fetchUser(userId);
const orders = await fetchOrders(user.id);
return { user, orders };
} catch (error) {
console.error('Failed to fetch:', error);
throw error;
}
}
// BAD - promise chains
function fetchUserData(userId) {
return fetchUser(userId)
.then(user => fetchOrders(user.id)
.then(orders => ({ user, orders })))
.catch(error => {
console.error('Failed to fetch:', error);
throw error;
});
}
Use Promise.all for parallel operations
// GOOD - parallel
const [users, products, orders] = await Promise.all([
fetchUsers(),
fetchProducts(),
fetchOrders(),
]);
// BAD - sequential when not needed
const users = await fetchUsers();
const products = await fetchProducts();
const orders = await fetchOrders();
Error Handling
Use custom errors
class NotFoundError extends Error {
constructor(resource, id) {
super(`${resource} with id ${id} not found`);
this.name = 'NotFoundError';
this.resource = resource;
this.id = id;
}
}
class ValidationError extends Error {
constructor(field, message) {
super(`${field}: ${message}`);
this.name = 'ValidationError';
this.field = field;
}
}
Always catch async errors
// GOOD
async function handleRequest(req, res) {
try {
const data = await processRequest(req);
res.json(data);
} catch (error) {
if (error instanceof ValidationError) {
res.status(400).json({ error: error.message });
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
}
Arrays
Use array methods over loops
// GOOD
const active = users.filter(u => u.active);
const names = users.map(u => u.name);
const total = items.reduce((sum, item) => sum + item.price, 0);
const hasAdmin = users.some(u => u.role === 'admin');
const allActive = users.every(u => u.active);
// BAD
const active = [];
for (let i = 0; i < users.length; i++) {
if (users[i].active) {
active.push(users[i]);
}
}
Use find for single items
// GOOD
const admin = users.find(u => u.role === 'admin');
// BAD
const admin = users.filter(u => u.role === 'admin')[0];
Objects
Use shorthand properties
// GOOD
const name = 'Alice';
const age = 30;
const user = { name, age };
// BAD
const user = { name: name, age: age };
Use computed property names
// GOOD
const key = 'dynamicKey';
const obj = {
[key]: 'value',
[`prefix_${key}`]: 'other value',
};
Use Object.entries/values/keys
// Iterate over object
for (const [key, value] of Object.entries(obj)) {
console.log(`${key}: ${value}`);
}
// Transform object to array
const values = Object.values(config);
const keys = Object.keys(config);
Modules
Use ES modules
// GOOD - named exports
export function formatDate(date) { ... }
export const MAX_SIZE = 1024;
// GOOD - default export for main item
export default class UserService { ... }
// GOOD - imports
import UserService, { formatDate, MAX_SIZE } from './user-service.js';
Organize exports in index files
// utils/index.js
export { formatDate, parseDate } from './date.js';
export { formatCurrency } from './currency.js';
export { validateEmail, validatePhone } from './validation.js';
// Usage
import { formatDate, formatCurrency, validateEmail } from './utils/index.js';
Optional Chaining and Nullish Coalescing
// GOOD - optional chaining
const city = user?.address?.city;
const first = items?.[0];
const result = obj?.method?.();
// GOOD - nullish coalescing
const name = user.name ?? 'Anonymous';
const count = data.count ?? 0;
// BAD
const city = user && user.address && user.address.city;
const name = user.name || 'Anonymous'; // Wrong for empty string