JavaScript Operations
Comprehensive reference for modern JavaScript and Node.js — async patterns, module systems, runtime internals, and ES2022-2025 features.
Async Decision Tree
What are you doing asynchronously?
│
├─ Simple one-off operation (DB query, HTTP call)
│ └─ async/await with try/catch ✓ default choice
│
├─ Multiple independent operations
│ ├─ All must succeed → Promise.all([a(), b(), c()])
│ ├─ Don't care about failures → Promise.allSettled([...])
│ └─ First one wins → Promise.race([...]) or Promise.any([...])
│
├─ Need external resolve/reject control (deferred)
│ └─ Promise.withResolvers() (ES2024)
│
├─ Processing a sequence of async values
│ ├─ Known array → for...of with await inside loop
│ └─ Unknown/infinite sequence → async generator + for await...of
│
├─ Large data / backpressure concerns
│ └─ Streams (ReadableStream / node:stream)
│ ├─ Transform data in flight → TransformStream / Transform
│ └─ Pipe chain → stream.pipeline() (Node) / pipeThrough() (Web)
│
├─ CPU-intensive work (would block event loop)
│ ├─ Short burst → offload with setTimeout(fn, 0) to yield
│ └─ Real work → Worker (browser) / worker_threads (Node)
│ └─ Shared memory needed → SharedArrayBuffer + Atomics
│
└─ Legacy code uses callbacks
└─ Wrap with util.promisify() (Node) or new Promise() constructor
Module System Decision Tree
Which module system should I use?
│
├─ New project / Node 18+
│ └─ ESM (set "type": "module" in package.json)
│ ├─ import / export syntax
│ ├─ Top-level await supported
│ └─ Better tree-shaking with bundlers
│
├─ Publishing a library
│ ├─ ESM-only → simplest, but breaks older CJS consumers
│ ├─ CJS-only → safe but no tree-shaking
│ └─ Dual package (recommended) → "exports" field with conditions
│ ├─ "import": "./dist/index.mjs"
│ └─ "require": "./dist/index.cjs"
│
├─ Existing CJS project, want ESM
│ ├─ Per-file migration → rename to .mjs, update require → import
│ ├─ Whole-project → add "type": "module", rename .cjs exceptions
│ └─ Keep CJS, add ESM wrapper → create thin .mjs re-export layer
│
├─ Browser (no bundler)
│ └─ Native ESM — <script type="module"> + importmap
│
└─ Need dynamic loading
└─ import() — works in both ESM and CJS files
├─ Lazy routes / code splitting
└─ Conditional platform code
Migration path: CJS → Dual → ESM-only
Event Loop Quick Reference
┌─────────────────────────────────────────────────────────┐
│ Call Stack │
│ (synchronous code executes here) │
└─────────────────────────┬───────────────────────────────┘
│ stack empty?
▼
┌─────────────────────────────────────────────────────────┐
│ Microtask Queue (drained fully) │
│ • Promise.then / .catch / .finally callbacks │
│ • queueMicrotask(fn) │
│ • MutationObserver callbacks (browser) │
│ • process.nextTick (Node — runs BEFORE other microtasks)│
└─────────────────────────┬───────────────────────────────┘
│ microtasks empty?
▼
┌─────────────────────────────────────────────────────────┐
│ Macrotask Queue (one task per loop tick) │
│ • setTimeout / setInterval callbacks │
│ • setImmediate (Node — runs in "check" phase) │
│ • I/O callbacks (network, file system) │
│ • requestAnimationFrame (browser) │
│ • MessagePort / Worker messages │
└─────────────────────────────────────────────────────────┘
Node.js event loop PHASES (libuv):
timers → pending callbacks → idle/prepare → poll → check → close callbacks
└─ process.nextTick + microtasks drain after EVERY phase
Key rules:
- Microtasks always run before the next macrotask
process.nextTick fires before other microtasks (Promise.then)
setImmediate fires in the "check" phase, after I/O callbacks
setTimeout(fn, 0) fires in "timers" phase — after I/O in same iteration
Modern JS Cheat Sheet (ES2022–2025)
| Feature |
Year |
Usage |
Array.at(-1) |
ES2022 |
Last element without .length - 1 |
Object.hasOwn(obj, key) |
ES2022 |
Replaces obj.hasOwnProperty(key) |
#privateField in class |
ES2022 |
True private (not just convention) |
static {} class block |
ES2022 |
One-time class initialization |
Top-level await |
ES2022 |
await at module top — ESM only |
Error cause |
ES2022 |
new Error('msg', { cause: err }) |
structuredClone(obj) |
ES2022 |
Deep clone — built-in, no lodash |
Array.findLast() |
ES2023 |
Find from end |
WeakMap(Symbol) |
ES2023 |
Symbols as WeakMap keys |
Object.groupBy(iter, fn) |
ES2024 |
Group into plain object |
Map.groupBy(iter, fn) |
ES2024 |
Group into Map |
Promise.withResolvers() |
ES2024 |
Deferred pattern |
ArrayBuffer.prototype.resize() |
ES2024 |
Grow/shrink buffer in-place |
String.prototype.isWellFormed() |
ES2024 |
Check valid Unicode |
import x from './f.json' with { type: 'json' } |
ES2024 |
Import attributes (assert {…} is the deprecated legacy syntax) |
Set.prototype.union(other) |
ES2025 |
Set algebra methods |
Set.prototype.intersection(other) |
ES2025 |
Set algebra methods |
Set.prototype.difference(other) |
ES2025 |
Set algebra methods |
Iterator helpers (map, filter, take…) |
ES2025 |
Lazy iterator protocol |
using / Symbol.dispose |
ES2025 |
Explicit resource management |
Temporal API |
Stage 3 |
Modern date/time (replaces Date) |
import defer |
Stage 3 |
Deferred module evaluation |
Node.js Quick Start
// Built-in test runner (Node 18+, stable in Node 20)
import { describe, it, before, after, mock } from 'node:test';
import assert from 'node:assert/strict';
describe('my module', () => {
it('adds numbers', () => {
assert.equal(1 + 1, 2);
});
});
// Run: node --test
// Watch: node --test --watch
// Coverage: node --test --experimental-test-coverage
// fs/promises — built-in, no third-party needed
import { readFile, writeFile, readdir } from 'node:fs/promises';
const content = await readFile('./config.json', 'utf8');
const files = await readdir('./src', { recursive: true }); // Node 18.17+
// .env loading — Node 21+ (no dotenv package required)
// node --env-file=.env server.js
Key built-in modules:
| Module |
Purpose |
node:fs/promises |
Async file system |
node:path |
Path manipulation |
node:url |
URL parsing, fileURLToPath |
node:crypto |
Hashing, encryption, UUIDs |
node:stream |
Streams + pipeline() |
node:worker_threads |
CPU parallelism |
node:child_process |
Subprocess execution |
node:test |
Built-in test runner |
node:http / node:http2 |
HTTP servers |
node:diagnostics_channel |
Observability hooks |
node:perf_hooks |
Performance measurement |
Error Handling Patterns
// 1. Standard async try/catch
async function fetchUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`, { cause: res });
return await res.json();
} catch (err) {
console.error('fetchUser failed:', err);
throw err; // re-throw unless you can recover
}
}
// 2. Abort with timeout (Node 17.3+ / browsers)
const signal = AbortSignal.timeout(5000);
const res = await fetch(url, { signal });
// 3. Global unhandled rejection handler
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection:', reason);
process.exit(1); // always exit — unknown state
});
// 4. AggregateError — wraps multiple errors
const results = await Promise.allSettled([a(), b(), c()]);
const failures = results.filter(r => r.status === 'rejected');
if (failures.length) {
throw new AggregateError(failures.map(f => f.reason), 'Multiple failures');
}
// 5. Custom Error class
class AppError extends Error {
constructor(message, { code, cause } = {}) {
super(message, { cause });
this.name = 'AppError';
this.code = code;
}
}
Common Gotchas
| Gotcha |
Why |
Fix |
this is undefined in callback |
Arrow functions capture this lexically; regular functions don't |
Use arrow function or .bind(this) |
Closure captures loop variable var i |
var is function-scoped; all closures share same i |
Use let i (block-scoped) or .forEach |
== treats null == undefined as true |
Loose equality does type coercion |
Always use === and !== |
0.1 + 0.2 !== 0.3 |
IEEE 754 floating-point precision |
Math.round(n * 1e10) / 1e10 or use Number.EPSILON comparison |
a?.b ?? c vs a?.b || c |
?? only falls back on null/undefined; || on any falsy |
Use ?? when 0 or "" are valid values |
typeof null === 'object' |
Historic JavaScript bug |
Check val === null explicitly |
[3,10,2].sort() → [10,2,3] |
Default sort converts to strings |
Provide comparator: .sort((a,b) => a - b) |
parseInt('08') → 8 in modern, 0 in old |
Octal parsing in pre-ES5 |
Always pass radix: parseInt(str, 10) |
for...in on arrays |
Iterates inherited enumerable properties too |
Use for...of or .forEach() for arrays |
Promise.all fails fast |
One rejection cancels all (others still run) |
Use Promise.allSettled if you need all results |
JSON.stringify drops undefined / functions / Symbols |
Not JSON-serializable |
Convert to null first or use a replacer function |
| Async function always returns a Promise |
async () => 42 returns Promise<42>, not 42 |
await the call site or chain .then() |
Reference Files
| File |
When to Load |
references/async-patterns.md |
Promise combinators, async iterators, AbortController, Streams, Web Workers, structured concurrency |
references/modules-runtime.md |
ESM/CJS/dual packages, dynamic import, V8 internals, memory management, event loop deep dive |
references/modern-features.md |
ES2022-2025 feature details, Proxy/Reflect, Decorators, Temporal, Explicit Resource Management |
references/node-patterns.md |
node:test runner, fs/promises, worker_threads, streams, crypto, graceful shutdown, permission model |
references/expert-insights.md |
Browser-side patterns: DOM batching, event delegation, debounce/throttle, memoization, lazy loading, XSS/CSP client security |
See Also
typescript-ops — TypeScript types, generics, utility types, tsconfig
react-ops — React hooks, Server Components, state management
vue-ops — Vue 3 Composition API, Pinia, Nuxt
testing-ops — Jest, Vitest, Playwright, TDD patterns
1---2name: javascript-ops3description: "JavaScript and Node.js patterns, async programming, modules, runtime internals, and modern ES2024+ features. Use for: javascript, js, node, nodejs, esm, commonjs, promise, async await, event loop, v8, npm, es6, es2024, worker threads, streams, event emitter, prototype, closure."4license: MIT5allowed-tools: "Read Write Bash"6metadata:7 author: claude-mods8 related-skills: typescript-ops, react-ops, vue-ops, testing-ops9---1011# JavaScript Operations1213Comprehensive reference for modern JavaScript and Node.js — async patterns, module systems, runtime internals, and ES2022-2025 features.1415---1617## Async Decision Tree1819```20What are you doing asynchronously?21│22├─ Simple one-off operation (DB query, HTTP call)23│ └─ async/await with try/catch ✓ default choice24│25├─ Multiple independent operations26│ ├─ All must succeed → Promise.all([a(), b(), c()])27│ ├─ Don't care about failures → Promise.allSettled([...])28│ └─ First one wins → Promise.race([...]) or Promise.any([...])29│30├─ Need external resolve/reject control (deferred)31│ └─ Promise.withResolvers() (ES2024)32│33├─ Processing a sequence of async values34│ ├─ Known array → for...of with await inside loop35│ └─ Unknown/infinite sequence → async generator + for await...of36│37├─ Large data / backpressure concerns38│ └─ Streams (ReadableStream / node:stream)39│ ├─ Transform data in flight → TransformStream / Transform40│ └─ Pipe chain → stream.pipeline() (Node) / pipeThrough() (Web)41│42├─ CPU-intensive work (would block event loop)43│ ├─ Short burst → offload with setTimeout(fn, 0) to yield44│ └─ Real work → Worker (browser) / worker_threads (Node)45│ └─ Shared memory needed → SharedArrayBuffer + Atomics46│47└─ Legacy code uses callbacks48 └─ Wrap with util.promisify() (Node) or new Promise() constructor49```5051---5253## Module System Decision Tree5455```56Which module system should I use?57│58├─ New project / Node 18+59│ └─ ESM (set "type": "module" in package.json)60│ ├─ import / export syntax61│ ├─ Top-level await supported62│ └─ Better tree-shaking with bundlers63│64├─ Publishing a library65│ ├─ ESM-only → simplest, but breaks older CJS consumers66│ ├─ CJS-only → safe but no tree-shaking67│ └─ Dual package (recommended) → "exports" field with conditions68│ ├─ "import": "./dist/index.mjs"69│ └─ "require": "./dist/index.cjs"70│71├─ Existing CJS project, want ESM72│ ├─ Per-file migration → rename to .mjs, update require → import73│ ├─ Whole-project → add "type": "module", rename .cjs exceptions74│ └─ Keep CJS, add ESM wrapper → create thin .mjs re-export layer75│76├─ Browser (no bundler)77│ └─ Native ESM — <script type="module"> + importmap78│79└─ Need dynamic loading80 └─ import() — works in both ESM and CJS files81 ├─ Lazy routes / code splitting82 └─ Conditional platform code83```8485**Migration path:** CJS → Dual → ESM-only8687---8889## Event Loop Quick Reference9091```92┌─────────────────────────────────────────────────────────┐93│ Call Stack │94│ (synchronous code executes here) │95└─────────────────────────┬───────────────────────────────┘96 │ stack empty?97 ▼98┌─────────────────────────────────────────────────────────┐99│ Microtask Queue (drained fully) │100│ • Promise.then / .catch / .finally callbacks │101│ • queueMicrotask(fn) │102│ • MutationObserver callbacks (browser) │103│ • process.nextTick (Node — runs BEFORE other microtasks)│104└─────────────────────────┬───────────────────────────────┘105 │ microtasks empty?106 ▼107┌─────────────────────────────────────────────────────────┐108│ Macrotask Queue (one task per loop tick) │109│ • setTimeout / setInterval callbacks │110│ • setImmediate (Node — runs in "check" phase) │111│ • I/O callbacks (network, file system) │112│ • requestAnimationFrame (browser) │113│ • MessagePort / Worker messages │114└─────────────────────────────────────────────────────────┘115116Node.js event loop PHASES (libuv):117 timers → pending callbacks → idle/prepare → poll → check → close callbacks118 └─ process.nextTick + microtasks drain after EVERY phase119```120121**Key rules:**122- Microtasks always run before the next macrotask123- `process.nextTick` fires before other microtasks (Promise.then)124- `setImmediate` fires in the "check" phase, after I/O callbacks125- `setTimeout(fn, 0)` fires in "timers" phase — after I/O in same iteration126127---128129## Modern JS Cheat Sheet (ES2022–2025)130131| Feature | Year | Usage |132|---------|------|-------|133| `Array.at(-1)` | ES2022 | Last element without `.length - 1` |134| `Object.hasOwn(obj, key)` | ES2022 | Replaces `obj.hasOwnProperty(key)` |135| `#privateField` in class | ES2022 | True private (not just convention) |136| `static {}` class block | ES2022 | One-time class initialization |137| Top-level `await` | ES2022 | `await` at module top — ESM only |138| `Error cause` | ES2022 | `new Error('msg', { cause: err })` |139| `structuredClone(obj)` | ES2022 | Deep clone — built-in, no lodash |140| `Array.findLast()` | ES2023 | Find from end |141| `WeakMap(Symbol)` | ES2023 | Symbols as WeakMap keys |142| `Object.groupBy(iter, fn)` | ES2024 | Group into plain object |143| `Map.groupBy(iter, fn)` | ES2024 | Group into Map |144| `Promise.withResolvers()` | ES2024 | Deferred pattern |145| `ArrayBuffer.prototype.resize()` | ES2024 | Grow/shrink buffer in-place |146| `String.prototype.isWellFormed()` | ES2024 | Check valid Unicode |147| `import x from './f.json' with { type: 'json' }` | ES2024 | Import attributes (`assert {…}` is the deprecated legacy syntax) |148| `Set.prototype.union(other)` | ES2025 | Set algebra methods |149| `Set.prototype.intersection(other)` | ES2025 | Set algebra methods |150| `Set.prototype.difference(other)` | ES2025 | Set algebra methods |151| Iterator helpers (`map`, `filter`, `take`…) | ES2025 | Lazy iterator protocol |152| `using` / `Symbol.dispose` | ES2025 | Explicit resource management |153| `Temporal` API | Stage 3 | Modern date/time (replaces Date) |154| `import defer` | Stage 3 | Deferred module evaluation |155156---157158## Node.js Quick Start159160```javascript161// Built-in test runner (Node 18+, stable in Node 20)162import { describe, it, before, after, mock } from 'node:test';163import assert from 'node:assert/strict';164165describe('my module', () => {166 it('adds numbers', () => {167 assert.equal(1 + 1, 2);168 });169});170171// Run: node --test172// Watch: node --test --watch173// Coverage: node --test --experimental-test-coverage174```175176```javascript177// fs/promises — built-in, no third-party needed178import { readFile, writeFile, readdir } from 'node:fs/promises';179180const content = await readFile('./config.json', 'utf8');181const files = await readdir('./src', { recursive: true }); // Node 18.17+182183// .env loading — Node 21+ (no dotenv package required)184// node --env-file=.env server.js185```186187**Key built-in modules:**188189| Module | Purpose |190|--------|---------|191| `node:fs/promises` | Async file system |192| `node:path` | Path manipulation |193| `node:url` | URL parsing, `fileURLToPath` |194| `node:crypto` | Hashing, encryption, UUIDs |195| `node:stream` | Streams + `pipeline()` |196| `node:worker_threads` | CPU parallelism |197| `node:child_process` | Subprocess execution |198| `node:test` | Built-in test runner |199| `node:http` / `node:http2` | HTTP servers |200| `node:diagnostics_channel` | Observability hooks |201| `node:perf_hooks` | Performance measurement |202203---204205## Error Handling Patterns206207```javascript208// 1. Standard async try/catch209async function fetchUser(id) {210 try {211 const res = await fetch(`/api/users/${id}`);212 if (!res.ok) throw new Error(`HTTP ${res.status}`, { cause: res });213 return await res.json();214 } catch (err) {215 console.error('fetchUser failed:', err);216 throw err; // re-throw unless you can recover217 }218}219220// 2. Abort with timeout (Node 17.3+ / browsers)221const signal = AbortSignal.timeout(5000);222const res = await fetch(url, { signal });223224// 3. Global unhandled rejection handler225process.on('unhandledRejection', (reason, promise) => {226 console.error('Unhandled rejection:', reason);227 process.exit(1); // always exit — unknown state228});229230// 4. AggregateError — wraps multiple errors231const results = await Promise.allSettled([a(), b(), c()]);232const failures = results.filter(r => r.status === 'rejected');233if (failures.length) {234 throw new AggregateError(failures.map(f => f.reason), 'Multiple failures');235}236237// 5. Custom Error class238class AppError extends Error {239 constructor(message, { code, cause } = {}) {240 super(message, { cause });241 this.name = 'AppError';242 this.code = code;243 }244}245```246247---248249## Common Gotchas250251| Gotcha | Why | Fix |252|--------|-----|-----|253| `this` is `undefined` in callback | Arrow functions capture `this` lexically; regular functions don't | Use arrow function or `.bind(this)` |254| Closure captures loop variable `var i` | `var` is function-scoped; all closures share same `i` | Use `let i` (block-scoped) or `.forEach` |255| `==` treats `null == undefined` as true | Loose equality does type coercion | Always use `===` and `!==` |256| `0.1 + 0.2 !== 0.3` | IEEE 754 floating-point precision | `Math.round(n * 1e10) / 1e10` or use `Number.EPSILON` comparison |257| `a?.b ?? c` vs `a?.b \|\| c` | `??` only falls back on `null`/`undefined`; `\|\|` on any falsy | Use `??` when 0 or `""` are valid values |258| `typeof null === 'object'` | Historic JavaScript bug | Check `val === null` explicitly |259| `[3,10,2].sort()` → `[10,2,3]` | Default sort converts to strings | Provide comparator: `.sort((a,b) => a - b)` |260| `parseInt('08')` → 8 in modern, 0 in old | Octal parsing in pre-ES5 | Always pass radix: `parseInt(str, 10)` |261| `for...in` on arrays | Iterates inherited enumerable properties too | Use `for...of` or `.forEach()` for arrays |262| `Promise.all` fails fast | One rejection cancels all (others still run) | Use `Promise.allSettled` if you need all results |263| `JSON.stringify` drops `undefined` / functions / Symbols | Not JSON-serializable | Convert to `null` first or use a replacer function |264| Async function always returns a Promise | `async () => 42` returns `Promise<42>`, not `42` | `await` the call site or chain `.then()` |265266---267268## Reference Files269270| File | When to Load |271|------|-------------|272| `references/async-patterns.md` | Promise combinators, async iterators, AbortController, Streams, Web Workers, structured concurrency |273| `references/modules-runtime.md` | ESM/CJS/dual packages, dynamic import, V8 internals, memory management, event loop deep dive |274| `references/modern-features.md` | ES2022-2025 feature details, Proxy/Reflect, Decorators, Temporal, Explicit Resource Management |275| `references/node-patterns.md` | node:test runner, fs/promises, worker_threads, streams, crypto, graceful shutdown, permission model |276| `references/expert-insights.md` | Browser-side patterns: DOM batching, event delegation, debounce/throttle, memoization, lazy loading, XSS/CSP client security |277278---279280## See Also281282- `typescript-ops` — TypeScript types, generics, utility types, tsconfig283- `react-ops` — React hooks, Server Components, state management284- `vue-ops` — Vue 3 Composition API, Pinia, Nuxt285- `testing-ops` — Jest, Vitest, Playwright, TDD patterns