Get the FREE Ultimate OpenClaw Setup Guide →

modern-javascript

npx machina-cli add skill ccheney/robust-skills/modern-javascript --openclaw
Files (1)
SKILL.md
11.4 KB

Modern JavaScript (ES6-ES2025)

Write clean, performant, maintainable JavaScript using modern language features. This skill covers ES6 through ES2025, emphasizing immutability, functional patterns, and expressive syntax.

Quick Decision Trees

"Which array method should I use?"

What do I need?
├─ Transform each element           → .map()
├─ Keep some elements               → .filter()
├─ Find one element                 → .find() / .findLast()
├─ Check if condition met           → .some() / .every()
├─ Reduce to single value           → .reduce()
├─ Get last element                 → .at(-1)
├─ Sort without mutating            → .toSorted()
├─ Reverse without mutating         → .toReversed()
├─ Group by property                → Object.groupBy()
└─ Flatten nested arrays            → .flat() / .flatMap()

"How do I handle nullish values?"

Nullish handling?
├─ Safe property access              → obj?.prop / obj?.[key]
├─ Safe method call                  → obj?.method?.()
├─ Default for null/undefined only   → value ?? 'default'
├─ Default for any falsy             → value || 'default'
├─ Assign if null/undefined          → obj.prop ??= 'default'
└─ Check property exists             → Object.hasOwn(obj, 'key')

"Should I mutate or copy?"

Always prefer non-mutating methods:
├─ Sort array      → .toSorted()    (not .sort())
├─ Reverse array   → .toReversed()  (not .reverse())
├─ Splice array    → .toSpliced()   (not .splice())
├─ Update element  → .with(i, val)  (not arr[i] = val)
├─ Add to array    → [...arr, item] (not .push())
└─ Merge objects   → {...obj, key}  (not Object.assign())

ES Version Quick Reference

VersionYearKey Features
ES62015let/const, arrow functions, classes, destructuring, spread, Promises, modules, Symbol, Map/Set, Proxy, generators
ES20162016Array.includes(), exponentiation operator **
ES20172017async/await, Object.values/entries, padStart/padEnd, trailing commas, SharedArrayBuffer, Atomics
ES20182018Rest/spread for objects, for await...of, Promise.finally(), RegExp named groups, lookbehind, dotAll flag
ES20192019.flat(), .flatMap(), Object.fromEntries(), trimStart/End(), optional catch binding, stable Array.sort()
ES20202020Optional chaining ?., nullish coalescing ??, BigInt, Promise.allSettled(), globalThis, dynamic import()
ES20212021String.replaceAll(), Promise.any(), logical assignment ??= and or=, numeric separators 1_000_000
ES20222022.at(), Object.hasOwn(), top-level await, private class fields #field, static blocks, Error.cause
ES20232023.toSorted(), .toReversed(), .toSpliced(), .with(), .findLast(), .findLastIndex(), hashbang grammar
ES20242024Object.groupBy(), Map.groupBy(), Promise.withResolvers(), RegExp v flag, resizable ArrayBuffer
ES20252025Iterator helpers (.map, .filter, .take), Set methods (.union, .intersection), RegExp.escape(), using/await using

Modernization Patterns

Array Access

// ❌ Legacy
const last = arr[arr.length - 1];
const secondLast = arr[arr.length - 2];

// ✅ Modern (ES2022)
const last = arr.at(-1);
const secondLast = arr.at(-2);

Non-Mutating Array Operations

// ❌ Mutates original array
const sorted = arr.sort((a, b) => a - b);
const reversed = arr.reverse();

// ✅ Returns new array (ES2023)
const sorted = arr.toSorted((a, b) => a - b);
const reversed = arr.toReversed();
const updated = arr.with(2, 'new value');
const removed = arr.toSpliced(1, 1);

String Replacement

// ❌ Legacy with regex
const result = str.replace(/foo/g, 'bar');

// ✅ Modern (ES2021)
const result = str.replaceAll('foo', 'bar');

Grouping Data

// ❌ Manual grouping
const grouped = items.reduce((acc, item) => {
  const key = item.category;
  acc[key] = acc[key] || [];
  acc[key].push(item);
  return acc;
}, {});

// ✅ Modern (ES2024)
const grouped = Object.groupBy(items, item => item.category);

Nullish Handling

// ❌ Falsy check (0, '', false are valid values)
const value = input || 'default';
const name = user && user.profile && user.profile.name;

// ✅ Nullish check (only null/undefined)
const value = input ?? 'default';
const name = user?.profile?.name;

Property Existence

// ❌ Can be fooled by prototype or overwritten hasOwnProperty
if (obj.hasOwnProperty('key')) { }

// ✅ Modern (ES2022)
if (Object.hasOwn(obj, 'key')) { }

Logical Assignment

// ❌ Verbose assignment
if (obj.prop === null || obj.prop === undefined) {
  obj.prop = 'default';
}

// ✅ Modern (ES2021)
obj.prop ??= 'default';  // Assign if null/undefined
obj.count ||= 0;         // Assign if falsy
obj.enabled &&= check(); // Assign if truthy

Async Patterns

Promise Combinators

// Wait for all, fail if any fails
const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()]);

// Wait for all, get status of each
const results = await Promise.allSettled([fetchA(), fetchB()]);
results.forEach(r => {
  if (r.status === 'fulfilled') console.log(r.value);
  else console.error(r.reason);
});

// First to succeed
const fastest = await Promise.any([fetchFromCDN1(), fetchFromCDN2()]);

// First to settle
const winner = await Promise.race([fetchData(), timeout(5000)]);

Promise.withResolvers (ES2024)

// ❌ Legacy pattern
let resolve, reject;
const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});

// ✅ Modern (ES2024)
const { promise, resolve, reject } = Promise.withResolvers();

Top-Level Await (ES2022)

// In ES modules, await at top level
const config = await fetch('/config.json').then(r => r.json());
const db = await connectDatabase(config);

export { db };

Functional Patterns

Immutable Object Updates

// Add/update property
const updated = { ...user, age: 31 };

// Remove property
const { password, ...userWithoutPassword } = user;

// Nested update
const updated = {
  ...state,
  user: { ...state.user, name: 'New Name' }
};

Array Transformations

// Chain transformations (ES2023)
const result = users
  .filter(u => u.active)
  .map(u => u.name)
  .toSorted();

// Using flatMap for filter+map (single pass)
const activeNames = users.flatMap(u => u.active ? [u.name] : []);

// ES2024: Group then process
const byStatus = Object.groupBy(users, u => u.active ? 'active' : 'inactive');
const activeNames = byStatus.active?.map(u => u.name) ?? [];

Composition

const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);

const processUser = pipe(
  user => ({ ...user, name: user.name.trim() }),
  user => ({ ...user, email: user.email.toLowerCase() }),
  user => ({ ...user, createdAt: new Date() })
);

Destructuring Patterns

Object Destructuring

// Basic with rename and default
const { name: userName, age = 18 } = user;

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

// Rest
const { id, ...userData } = user;

Array Destructuring

// Skip elements
const [first, , third] = array;

// Rest
const [head, ...tail] = array;

// Swap variables
[a, b] = [b, a];

// Function returns
const [x, y] = getCoordinates();

Anti-Patterns

Anti-PatternProblemModern Solution
arr[arr.length-1]Verbose, error-pronearr.at(-1)
.sort() on originalMutates array.toSorted()
.replace(/g/) for allRegex overhead.replaceAll()
obj.hasOwnProperty()Can be overwrittenObject.hasOwn()
value || default0, '', false treated as falsyvalue ?? default
obj && obj.prop && obj.prop.method()Verbose null checksobj?.prop?.method?.()
for (let i = 0; ...)Index bugs, verbose.map(), .filter(), for...of
new Promise((res, rej) => ...)BoilerplatePromise.withResolvers()
Manual array groupingVerbose, error-proneObject.groupBy()

Best Practices

  1. Use const by default — Only use let when reassignment is needed
  2. Prefer arrow functions — Especially for callbacks and short functions
  3. Use template literals — Instead of string concatenation
  4. Destructure early — Extract what you need at function start
  5. Avoid mutations — Use .toSorted(), .toReversed(), spread operator
  6. Use optional chaining — Prevent "Cannot read property of undefined"
  7. Use nullish coalescing?? for defaults, not || (unless intentional)
  8. Prefer array methods.map(), .filter(), .find() over loops
  9. Use async/await — Instead of .then() chains
  10. Handle errors properlytry/catch with async/await

Reference Documentation

ES Version References

FilePurpose
references/ES2016-ES2017.mdincludes, async/await, Object.values/entries, string padding
references/ES2018-ES2019.mdrest/spread objects, flat/flatMap, RegExp named groups
references/ES2022-ES2023.md.at(), .toSorted(), .toReversed(), .findLast(), class features
references/ES2024.mdObject.groupBy, Promise.withResolvers, RegExp v flag
references/ES2025.mdSet methods, iterator helpers, using/await using
references/UPCOMING.mdTemporal API, Decorators, Decorator Metadata

Pattern References

FilePurpose
references/PROMISES.mdPromise fundamentals, async/await, combinators
references/CONCURRENCY.mdParallel, batched, pool patterns, retry, cancellation
references/IMMUTABILITY.mdImmutable data patterns, pure functions
references/COMPOSITION.mdHigher-order functions, memoization, monads
references/CHEATSHEET.mdQuick syntax reference

Resources

Specifications

Documentation

Compatibility

Source

git clone https://github.com/ccheney/robust-skills/blob/main/skills/modern-javascript/SKILL.mdView on GitHub

Overview

Write clean, performant, maintainable JavaScript using modern language features. This skill covers ES6 through ES2025, emphasizing immutability, functional patterns, and expressive syntax. It focuses on safe property access, non-mutating operations, and concise constructs like arrow functions and destructuring.

How This Skill Works

Developers apply the ES6-ES2025 toolkit by preferring non-mutating array methods (toSorted, toReversed, with, spread) and by using modern syntax (optional chaining, nullish coalescing, destructuring). They integrate functional patterns (map, filter, reduce) and object/groupBy helpers to write declarative code. The skill also references ES version guidance and practical decision trees to choose the right feature for readability and performance.

When to Use It

  • Writing new JavaScript code for web applications or Node.js services
  • Refactoring legacy JavaScript code to modern patterns and syntax
  • Modernizing a codebase to leverage ES6-ES2025 features and best practices
  • Implementing functional programming patterns to improve maintainability
  • Code reviews focused on performance, readability, and safety of JS

Quick Start

  1. Step 1: Audit existing code for mutating patterns and identify targets for ES6-ES2025 features
  2. Step 2: Refactor to non-mutating methods, add optional chaining and nullish coalescing where needed
  3. Step 3: Run tests and linting, measure performance, and iterate on readability

Best Practices

  • Always prefer non-mutating methods: use toSorted(), toReversed(), toSpliced(), with(), and spread instead of sort(), reverse(), splice(), or in-place mutations
  • Use optional chaining, nullish coalescing, and safe property access to handle nested data gracefully
  • Adopt functional patterns: map, filter, reduce with immutable data flows; utilize Object.groupBy when grouping data
  • Leverage ES2022+ features like .at() and top-level await to simplify code paths and readability
  • Write expressive, concise code with destructuring, template literals, and arrow functions to improve clarity

Example Use Cases

  • Refactor a legacy data transformation to use map/filter/reduce with immutable data structures and no in-place mutations
  • Replace mutations in a UI state manager with non-mutating array ops (toSorted, toReversed, with) and spreads
  • Group user data with Object.groupBy for rendering sections, dashboards, or reports
  • Safely render deeply nested JSON using optional chaining and nullish coalescing to provide defaults
  • Adopt ES2025 features like .at() and .toSorted() in a sorting routine to ensure non-mutating behavior

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers