Free to list, always.No paid rankings. Every recommendation explains its trade-offs.
OpenSourceChoice
Frontend · Intermediate

Modern JavaScript: ES6 and Beyond

Learn modern JavaScript modules, destructuring, async/await, safer defaults, and review practices you can apply to current codebases.

2 hoursUpdated Jul 21, 2026456 views

Adopt modern JavaScript syntax you will see in every current codebase: modules, destructuring, async/await, and safer defaults.

What you will learn

  • let/const, arrow functions, template literals
  • Destructuring, rest, and spread
  • Promises and async/await with real error handling
  • ES modules in Node and browsers

Prerequisites

  • Basic JavaScript: variables, functions, arrays, objects, and the DOM or Node REPL

Why “modern” still matters

Most tutorials still mix var, callbacks, and CommonJS. Production code expects block scoping, modules, and async/await. Learn the current defaults once, then read older snippets with a translation layer in your head.

Start here: MDN JavaScript Guide.

Syntax you should know cold

const user = { id: 1, name: "Ada", roles: ["admin", "editor"] };
const { name, roles: [primaryRole, ...otherRoles] } = user;
const copy = { ...user, name: "Grace" };

const add = (a, b) => a + b;
const message = `Hello, ${name} (${primaryRole})`;

const unique = [...new Set([1, 1, 2, 3])];

Prefer const by default; use let only when you reassign. Avoid var.

Async flow

async function loadProject(slug) {
  const response = await fetch(`/api/projects/${slug}`);
  if (!response.ok) {
    throw new Error(`request_failed:${response.status}`);
  }
  return response.json();
}

async function main() {
  try {
    const project = await loadProject("postgres");
    console.log(project.name);
  } catch (error) {
    console.error(error);
  }
}

Docs: async function and Using promises.

Modules

// math.js
export function clamp(value, min, max) {
  return Math.min(max, Math.max(min, value));
}

// app.js
import { clamp } from "./math.js";
console.log(clamp(120, 0, 100));

In browsers, use <script type="module">. In Node, prefer "type": "module" in package.json or .mjs files. See Node.js ES modules.

Useful extras

  • Optional chaining: user.profile?.city
  • Nullish coalescing: value ?? "default" (only null/undefined)
  • Array.prototype.map / filter / reduce instead of manual index loops when clarity wins

Practice checklist

  • Rewrite a callback-based fs.readFile example with async/await (or fs/promises)
  • Split a script into two modules with one named export
  • Destructure a nested API response safely with optional chaining

Common pitfalls

  • Treating ?? like || (empty string and 0 are valid values)
  • Forgetting await and logging a Promise object
  • Mixing CommonJS require and ESM import in the same file without a migration plan

Sources and further reading

Who this guide is for

  • JavaScript developers modernizing legacy code
  • Frontend and Node.js engineers aligning language conventions
  • Reviewers assessing readability and runtime compatibility

Definition of done

A small modernized module that uses clear data transformations, explicit async error handling, compatible module boundaries, and tests that preserve behavior through the refactor.

Finishing the commands is not the same as finishing the guide. Keep the result only when another person can reproduce it, inspect the evidence, and understand the remaining risks.

Professional workflow

Phase 1 — Establish compatibility

Goal: Know which syntax and APIs the supported runtimes understand.

  • List browser or Node.js support targets.
  • Separate language syntax from newer web or runtime APIs.
  • Run existing tests before changing style.

Verification: The support matrix and transpilation policy are documented next to the build configuration.

Phase 2 — Modernize for clarity

Goal: Use modern features only where intent becomes easier to read.

  • Prefer const and narrow scopes.
  • Use destructuring and iteration without hiding meaningful names.
  • Replace promise chains with async functions when error flow becomes clearer.

Verification: The refactor changes no externally observable behavior and reduces cognitive branching.

Phase 3 — Strengthen module boundaries

Goal: Make imports, exports, and errors predictable.

  • Expose a small public API.
  • Avoid hidden global mutation.
  • Test rejected promises, empty data, and malformed input.

Verification: Consumers can use the module without relying on internal object shape or execution order.

Review questions before you continue

  • Which runtimes must execute this code directly?
  • Does the proposed syntax improve intent or only shorten lines?
  • Where should asynchronous errors be handled?
  • Can mutation be localized rather than eliminated through expensive copying?

If an answer is unknown, record it as an explicit follow-up instead of hiding the uncertainty behind a successful demo.

Troubleshooting decision guide

  • An import fails: confirm ESM/CommonJS mode, file extensions, package exports, and runtime support.
  • Async errors disappear: await the promise or return it to the caller and test rejection paths.
  • Destructuring throws: validate nullable external input before unpacking it.
  • A refactor becomes slower: measure the hot path instead of assuming a functional style is free.

Change one variable at a time, preserve the first useful error, and write down the command or condition that fixed it. That turns a private debugging session into team knowledge.

Production-readiness checklist

  • Pin and document runtime support.
  • Lint for unsafe patterns without turning style into noise.
  • Validate all external input.
  • Keep source maps available for controlled debugging.
  • Test both success and failure behavior at public module boundaries.

These checks are intentionally stricter than a beginner demo. Use them to decide whether to continue learning, run a controlled pilot, or request specialist review.

Your next 30 minutes

  • Choose one legacy module with strong existing tests.
  • Modernize it without changing the public API.
  • Review bundle/runtime compatibility and error paths.
  • Adopt only the conventions that improved the review.

CTA: Pick the first unchecked step, complete it now, and save the evidence in your project README or decision log. Then open the official documentation linked below before adopting the result in production.

Similar guides

Keep learning in the same lane.

Matched by technology, category, tags, and connected software—not ranked sponsorship.

IntermediateBuilding a Todo App with React and TypeScript

Build a typed React and TypeScript Todo app with safe state updates, reusable components, persistence, tests, and production checks.

React · 2 hours
BeginnerGetting Started with React Hooks

Use React Hooks for state, effects, derived values, and reusable logic while avoiding common dependency and cleanup bugs.

React · 1 hour
BeginnerCSS Grid Layout: A Complete Guide

Master CSS Grid tracks, placement, responsive patterns, accessibility checks, and debugging techniques using standards-based examples.

CSS · 2 hours
Browse the full library →
Turn reading into evidence

Make this guide useful today.

Complete one verification step, save the result in your project notes, and decide whether you are ready for the next phase. A reproducible result is more valuable than a finished page.

  • Run the smallest working example.
  • Test one failure or edge case.
  • Record the command, result, and remaining risk.