DS DevShelfHub Projects · AI tools
Cheatsheets / JavaScript
Cheatsheet · Languages

JavaScript: ES2024 Async, Destructuring and Classes Reference Guide

By DevShelfHub

Modern JS — destructuring, spread, classes, async/await, modules, iterators, the bits of the language you actually use.

96 items ◷ 8 min ES2024 Async Iterators

Start hereQuick start · 6 you’ll reach for daily

Destructureconst {a, b} = obj
Spread{...a, x: 1}
Optionaluser?.address?.city
Nullishx ?? fallback
Parallelawait Promise.all([…])
Iteratefor await (const x of stream)

Target versions · paceVersions

Targets: ES2024 node ≥ 20 browsers: modern evergreen

Snippets pin to ES2024 (Node 20+, all evergreen browsers). Top-level await, structuredClone, Array.prototype.toSorted / toReversed, Object.groupBy / Map.groupBy all shipped recently. If you target older browsers, transpile with Babel / SWC and check caniuse.

runtime · package managerSetup

bash
# Node project
npm init -y
echo '{"type":"module"}' > package.json     # opt into ESM

# Run a script
node src/index.js                            # ESM, top-level await OK in 20+
node --watch src/index.js                    # built-in watch mode (Node 18+)

# Or pick a faster runtime
bun  run src/index.js                         # bun: native JS/TS + bundler + test runner
deno run src/index.js                         # deno: secure-by-default, web APIs

# Package manager picks
npm install lodash-es                         # widest compatibility
pnpm add lodash-es                            # fastest in monorepos
bun  add lodash-es                            # fastest single-package

# Format + lint
npx eslint . --fix
npx prettier --write .

modules & specifiersCommon imports

import x from "./mod.js"Default import.
import { a, b } from "./mod.js"Named imports.
import * as M from "./mod.js"Namespace import.
import x, { a } from "./mod.js"Default + named.
const { readFile } = await import("node:fs/promises")Dynamic import. Returns a Promise.
import data from "./data.json" with { type: "json" }Import attribute (ES2024).
import fs from "node:fs/promises"node: prefix for stdlib.
export const x = … / export default …Named vs default export.
export * from "./other.js"Re-export.
{ "type": "module" } in package.jsonMake .js ESM by default.

bindings & primitivesVariables & values

const x = 1Preferred Block-scoped, no rebind. Default.
let x = 1Block-scoped, mutable.
var x = 1Legacy Function-scoped, hoisted. Avoid.
typeof x"string", "number", "boolean", "bigint", "symbol", "function", "object", "undefined".
x === yStrict equality. Always prefer over ==.
x ?? yNullish coalescing: null or undefined only.
x ||= y / x &&= y / x ??= yLogical assignment.
Number.isFinite(x) / Number.isNaN(x)Type-safe checks. Skip the global versions.
BigInt(x) / 10nArbitrary-precision int.
Symbol("k") / Symbol.for("k")Unique tags / global registry.
structuredClone(value)Deep clone, handles cyclic / typed arrays.

unpack · spread · restDestructuring & spread

javascript
// Object destructuring with rename + default
const { name: who = "anon", age } = user;

// Nested + rest
const { profile: { email }, ...rest } = user;

// Array destructuring + skip
const [first, , third, ...tail] = [1, 2, 3, 4, 5];

// Function param destructuring (with defaults)
function greet({ name = "anon", lang = "en" } = {}) {
  return `Hi ${name} (${lang})`;
}

// Spread / rest
const merged   = { ...a, ...b, override: 1 };
const appended = [...arr, 4, 5];

function sum(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}

// Object shorthand + computed keys
const key = "id";
const obj = { name, age, [key]: 42 };

// Optional chaining + nullish coalescing
const city = user?.address?.city ?? "unknown";

// Logical assignment
config.port ??= 3000;      // assign only if nullish
flags.dev  ||= true;       // assign only if falsy
guard.ok   &&= sanitize(); // reassign only if truthy

arrows & closuresFunctions

function fn(x) { … }Hoisted, has its own this.
const fn = x => x * 2Arrow. No own this / arguments.
const fn = (x, y) => ({ x, y })Parens around object literal in concise body.
function fn(a, b = 1, …rest) { … }Default + rest params.
fn.bind(thisArg, …args)Partially apply + lock this.
function* gen() { yield 1 }Generator. Lazy sequence.
async function fn() { … }Returns a Promise. await inside.
async function* fn() { yield x }Async generator. Iterate with for await.
new Function("a", "b", "return a+b")Avoid Eval-equivalent.

stdlib essentialsArrays & objects

Array methods

arr.map(fn) / .filter(fn) / .reduce(fn, init)Functional core.
arr.find(fn) / .findIndex(fn) / .findLast(fn)Single match.
arr.some(fn) / .every(fn)Boolean reductions. Short-circuit.
arr.flat(depth) / .flatMap(fn)Flatten one (or N) levels.
arr.includes(x) / arr.indexOf(x)Membership. includes handles NaN.
arr.at(-1)Negative indexing.
arr.toSorted() / .toReversed() / .toSpliced()Preferred Non-mutating; new array.
arr.sort() / .reverse() / .splice()Mutating Use only when you want in-place.
Array.from(iter, fn)Materialise an iterable, with optional map.
Array.of(1, 2, 3)Tuple constructor. Array(3) creates a hole.
Object.groupBy(arr, fn)Group items by key (ES2024).

Object utilities

Object.keys(o) / values(o) / entries(o)Iterate own enumerable.
Object.fromEntries(pairs)Inverse of entries.
Object.assign(target, src)Shallow merge. Mutates target.
Object.freeze(o) / isFrozen(o)Shallow immutability.
Object.hasOwn(o, k)Preferred over hasOwnProperty.
Object.create(null)Prototype-free map. Avoids prototype pollution.
structuredClone(o)Deep clone. Beats JSON.parse(JSON.stringify(…)).

promises & awaitAsync

async fn() { const x = await p }Wait on a Promise inside an async function.
Promise.all([p1, p2])Wait all; reject on first failure.
Promise.allSettled([…])Wait all, never short-circuit.
Promise.race([…])First to settle wins (resolve or reject).
Promise.any([…])First fulfilment; rejects only if all fail.
new AbortController()Cancel fetch, listeners, timers.
setTimeout(fn, ms) / queueMicrotask(fn)Macrotask / microtask. Microtasks drain before next macrotask.
AbortSignal.timeout(ms)Auto-aborting signal. Built-in.
for await (const x of asyncIter) { … }Iterate async sources (streams, RxJS Observables).
javascript
// Promise basics — sequential
const user = await fetch("/api/users/1").then(r => r.json());

// Parallel: fire requests together, await both
const [u, posts] = await Promise.all([
  fetch("/api/users/1").then(r => r.json()),
  fetch("/api/users/1/posts").then(r => r.json()),
]);

// Race: first to settle wins (incl. rejection)
const fast = await Promise.race([fetchA(), fetchB()]);

// allSettled: collect outcomes, don't short-circuit
const results = await Promise.allSettled(urls.map(u => fetch(u)));
for (const r of results) {
  if (r.status === "fulfilled") console.log(r.value);
  else                          console.warn(r.reason);
}

// Timeout pattern with AbortController
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 5_000);
try {
  const res = await fetch(url, { signal: ctrl.signal });
} finally {
  clearTimeout(t);
}

// Async iteration
for await (const chunk of stream) {
  process(chunk);
}

OO syntaxClasses

class C { constructor() { … } }Constructor + prototype methods under the hood.
class C extends Base { … }Single-parent inheritance.
super(…) / super.method()Call parent constructor / method.
#fieldTruly private — not accessible outside the class.
static prop / static method() / static { … }Class-level state + init block.
get prop() {} / set prop(v) {}Computed properties.
obj instanceof CClass identity check.
class C { *[Symbol.iterator]() { yield… } }Make instances iterable.
javascript
class User {
  // Private fields (real privacy, not convention)
  #id;
  static #count = 0;

  // Public field with default
  active = true;

  constructor(name, email) {
    this.name  = name;
    this.email = email;
    this.#id   = ++User.#count;
  }

  // Getter / setter
  get id()      { return this.#id; }
  set email(v)  { if (!v.includes("@")) throw new Error("bad"); this._email = v; }
  get email()   { return this._email; }

  // Method
  greet() { return `Hi ${this.name}`; }

  // Static
  static fromRow(row) { return new User(row.name, row.email); }

  // Static block (one-time setup at class load)
  static { console.log("User class loaded"); }
}

class Admin extends User {
  constructor(name, email, level) {
    super(name, email);
    this.level = level;
  }
  greet() { return `Admin: ${super.greet()}`; }
}

const u = User.fromRow({ name: "Ada", email: "a@b.c" });
u instanceof User;                  // true
Object.hasOwn(u, "name");          // true

loops & collectionsIteration & collections

for (const x of iter) { … }Preferred for arrays + iterables.
for (const k in obj) { … }Iterates enumerable string keys. Rarely the right call.
map.forEach((v, k) => …) / set.forEach(…)Side-effect iteration over collections.
new Map() / new Set()Real maps / sets. Key by anything, including objects.
map.set(k, v) / map.get(k) / map.has(k)Map ops.
set.add(x) / set.has(x) / set.delete(x)Set ops.
new WeakMap() / new WeakSet()Don’t prevent GC. Use for caches keyed by objects.
[...new Set(arr)]Dedupe an array.
Map.groupBy(arr, fn)Group into a real Map (ES2024).

throw, catch, causeErrors

throw new Error("msg")Always throw Error instances, not strings.
throw new TypeError("…") / RangeError / SyntaxErrorBuilt-in error subclasses.
class MyError extends ErrorCustom error. Always set this.name.
try { … } catch (e) { … } finally { … }finally runs whether or not we threw.
try { … } catch { … }Catch without binding the error.
new Error("wrap", { cause: err })Wrap an underlying error.
new AggregateError([e1, e2], "all failed")Thrown by Promise.any on total failure.
Error.captureStackTrace(this, MyError)Cleaner stack in custom errors (Node).

things to rememberRuntime quirks

typeof null === "object"Long-standing oddity. Use x === null.
NaN !== NaNUse Number.isNaN.
0 === -0True. Use Object.is to distinguish.
[] == false // trueLoose equality madness. Always ===.
JSON.stringify(undefined) === undefinedNot a string; tripping point for transport.
setTimeout(fn, 0)Min 4ms in many engines. Use queueMicrotask for “asap”.
delete arr[i]Leaves a hole, doesn’t shrink length. Use splice or filter.
Date getMonth() is 0-indexedJanuary is 0. Use Temporal when available.

timeout · retry · concurrencyEnd-to-end · Rate-limited fetcher

AbortController + exponential backoff + bounded parallelism. The four-tool kit you reach for whenever you’re wrangling an external API.

javascript
// Tiny rate-limited fetcher: AbortController + retry + concurrency limit.
async function fetchJSON(url, { retries = 2, timeout = 5_000 } = {}) {
  for (let attempt = 0; attempt <= retries; attempt++) {
    const ctrl = new AbortController();
    const t    = setTimeout(() => ctrl.abort(), timeout);
    try {
      const res = await fetch(url, { signal: ctrl.signal });
      if (!res.ok) throw new Error(`${res.status} ${url}`);
      return await res.json();
    } catch (err) {
      if (attempt === retries) throw err;
      await new Promise(r => setTimeout(r, 2 ** attempt * 200));   // backoff
    } finally {
      clearTimeout(t);
    }
  }
}

async function mapLimit(items, limit, fn) {
  const out = new Array(items.length);
  let i = 0;
  await Promise.all(
    Array.from({ length: limit }, async () => {
      while (i < items.length) {
        const idx = i++;
        out[idx] = await fn(items[idx], idx);
      }
    }),
  );
  return out;
}

const urls    = ["/a", "/b", "/c", "/d", "/e"];
const results = await mapLimit(urls, 3, fetchJSON);
console.log(results);

Best practiceGood to know

Use the non-mutating Array methods. toSorted / toReversed / toSpliced were added precisely so you stop accidentally mutating shared state. Default to them.
structuredClone beats JSON.parse(JSON.stringify(…)). Handles Maps, Sets, Dates, typed arrays, cycles — and is faster. The JSON round-trip silently drops undefined, functions, and symbols.
Always pass an AbortSignal to fetch. Without one, a hung network call holds the whole pipeline. AbortSignal.timeout(ms) is one line.

Common trapsWatch out for

Arrow functions don’t bind their own this. Great for callbacks; wrong for methods you want to call with a different receiver. Mixing the two is the #1 “this is undefined” bug.
Spread is shallow. {...a} copies one level. Nested objects still share references — mutate one, the other sees it. Reach for structuredClone or an immutable lib.
Forgetting await inside a loop. [1,2,3].map(async …) returns Promises, not results. Use Promise.all(arr.map(async …)) or a for loop with await.

Go deeperSee also

JavaScript FAQ

What is JavaScript used for?

JavaScript is the language of the web — it runs in every browser and, via Node.js or Deno, on the server. It powers interactive frontends (React, Vue, Svelte), REST and GraphQL APIs, CLI tools, and serverless functions. ES2024 and Node 20+ are the current baseline.

What is the difference between let, const, and var in JavaScript?

var is function-scoped and hoisted, which causes surprising bugs. let and const are block-scoped and are the modern replacements. Use const for values that are never reassigned (including object and array references); use let when the binding must change.

How does async/await work in JavaScript?

async/await is syntactic sugar over Promises. An async function always returns a Promise; await pauses execution inside the function until the awaited Promise resolves or rejects. Wrap await calls in try/catch to handle errors, or attach .catch() to the returned Promise.

What is optional chaining in JavaScript?

Optional chaining (?.) short-circuits to undefined instead of throwing a TypeError when accessing a property on null or undefined. user?.address?.city returns undefined safely if user or address is null. Use it with the nullish coalescing operator (??) to provide a fallback.

What is the difference between == and === in JavaScript?

=== (strict equality) checks value and type with no coercion: 0 === false is false. == (loose equality) coerces types before comparing: 0 == false is true. Always use === unless you deliberately want type coercion — loose equality is a common source of bugs.