agentsclimarketplace

Modern javascript

Skill ccheney/robust-skills/skills/modern-javascript

Robust skills for Agents

Install
npx -y skills add ccheney/robust-skills --skill modern-javascript

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

Proactively apply when creating web applications, Node.js services, or any JavaScript project. Triggers on JavaScript, ES6, ES2020, ES2022, ES2024, ES2025, ES2026, modern JS, refactor legacy, array methods, async/await, optional chaining, nullish coalescing, destructuring, spread, rest, template literals, arrow functions, toSorted, toReversed, at, groupBy, iterator helpers, Set union intersection difference, Promise, Promise.try, RegExp.escape, Array.fromAsync, using await using, Temporal, functional programming. Use when writing new JavaScript code, refactoring legacy code, modernizing codebases, implementing functional patterns, checking which ECMAScript edition or runtime supports a feature, or reviewing JS for performance and readability. Modern JavaScript (ES6-ES2026) patterns and best practices.

SKILL.md

15.3 KB, as published. Nobody here has run it

Modern JavaScript (ES6-ES2026)

Write clean, performant, maintainable JavaScript using modern language features. This skill covers ES6 through ES2026, emphasizing immutability, functional patterns, and expressive syntax — and it pins each feature to the ECMAScript edition and runtimes that actually support it, because misattributed versions are the most common way modern-JS advice breaks.

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() / Map.groupBy()
├─ Collect an async iterable        → Array.fromAsync()
└─ 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

Editions are set by which proposals reach TC39 Stage 4 before the spring cutoff, so a feature's edition often lags its browser availability (e.g. Array.fromAsync shipped in browsers in 2023-24 but is spec'd in ES2026). Cite editions from this table, not from memory.

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(), matchAll
ES20212021String.replaceAll(), Promise.any(), logical assignment ??= ||= &&=, numeric separators 1_000_000, WeakRef
ES20222022.at(), Object.hasOwn(), top-level await, private class fields #field, static blocks, Error cause, /d flag
ES20232023.toSorted(), .toReversed(), .toSpliced(), .with(), .findLast(), .findLastIndex(), hashbang grammar
ES20242024Object.groupBy(), Map.groupBy(), Promise.withResolvers(), RegExp /v flag, resizable ArrayBuffer + transfer(), Atomics.waitAsync(), isWellFormed()
ES20252025Set methods (.union, .intersection, ...), iterator helpers (.map, .filter, .take), RegExp.escape(), Promise.try(), import attributes + JSON modules, duplicate named capture groups, RegExp inline modifiers, Float16Array
ES20262026Array.fromAsync(), Error.isError(), Math.sumPrecise(), Uint8Array.fromBase64()/toBase64(), Iterator.concat(), Map.getOrInsert(), JSON.parse source access

Already Stage 4 and slated for ES2027: Temporal, explicit resource management (using/await using), Atomics.pause(), Iterator.zip(). Decorators remain Stage 3 (transpiler only). Records & Tuples (#{}/#[]) was withdrawn — never emit that syntax. See references/UPCOMING.md.

Runtime Support Rules of Thumb

State the baseline when reaching for newer features; suggesting .toSorted() to someone on Node 18 produces a runtime TypeError: arr.toSorted is not a function.

  • Safe almost everywhere (Baseline widely available): everything through ES2023, plus ES2024 grouping/Promise.withResolvers and Array.fromAsync (Node 21-22+, Safari 16.4-17.4+).
  • Needs a modern baseline (2024-2025 browsers, Node 22-23+): Set methods, iterator helpers, import attributes, Promise.try, duplicate named capture groups.
  • Cutting edge (Node 24+, 2025+ browsers; Safari partly behind flags): RegExp.escape, Float16Array, using/await using, Error.isError, Uint8Array base64.
  • Native but not universal: Temporal (Firefox 139+, Chrome 144+, Node 26+; polyfill elsewhere). Decorators always need Babel or TypeScript.

Per-feature version tables live at the end of references/ES2024.md, references/ES2025.md, and references/ES2026.md.

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 (and returns the same reference)
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) — no escaping worries for literal strings
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);

Gotcha: Object.groupBy returns a null-prototype object — property access and destructuring work, but grouped.hasOwnProperty(...) throws. Use Object.hasOwn(). Use Map.groupBy when keys are objects or non-strings. There is no Array.prototype.group() — that earlier shape of the proposal never shipped.

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) — also works on null-prototype objects
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 newState = {
  ...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 activeUserNames = 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(/x/g) for literal stringsNeeds regex escaping.replaceAll()
obj.hasOwnProperty()Can be overwritten; throws on null-prototype objectsObject.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
items.forEach(async item => ...)forEach ignores promises; nothing is awaitedfor...of + await, or Promise.all(items.map(...))
let resolve; new Promise(r => ...)Boilerplate, escape-hatch closuresPromise.withResolvers()
Manual array grouping with reduceVerbose, error-proneObject.groupBy() / Map.groupBy()
#{ } / #[ ] record/tuple literalsProposal withdrawn April 2025; never valid syntaxPlain frozen objects, or Maps keyed manually

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. State the runtime baseline — Note the Node/browser floor when using post-ES2023 features

Reference Documentation

Open the reference that matches the task; SKILL.md alone is enough for everyday syntax choices.

ES Version References

Read thisWhen
references/ES2016-ES2017.mdasync/await fundamentals, Object.values/entries, string padding
references/ES2018-ES2019.mdObject rest/spread, flat/flatMap, regex named groups/lookbehind/dotAll
references/ES2022-ES2023.md.at(), change-by-copy methods, private fields, static blocks, error cause
references/ES2024.mdGrouping data, Promise.withResolvers, /v regex, ArrayBuffer resize/transfer
references/ES2025.mdSet operations, iterator helpers, RegExp.escape, Promise.try, import attributes
references/ES2026.mdArray.fromAsync, Error.isError, base64 bytes, Math.sumPrecise, Map.getOrInsert
references/UPCOMING.mdTemporal, using/await using, decorators, proposal status questions

Pattern References

Read thisWhen
references/PROMISES.mdWriting async flows, choosing combinators, fixing promise anti-patterns
references/CONCURRENCY.mdRate limiting, pools, retry/backoff, timeouts, cancellation, async iteration
references/IMMUTABILITY.mdState updates (React/Redux), pure functions, deep clone decisions
references/COMPOSITION.mdHigher-order functions, currying, memoization, Maybe/Result patterns
references/CHEATSHEET.mdQuick syntax lookup across all editions

Resources

Specifications

Documentation

Compatibility

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.