JavaScript · ES2026

ES17 Interactive Hub — ECMAScript 2026

Explicit Resource Management (using/await using with Symbol.dispose), cross-realm-safe Error.isError(), inline RegExp pattern modifiers (?i:...) scope flags, Iterator.range/zip — better ergonomics + safer resource management.

Experience
0 XP
Course completion0%

Explicit Resource Management (using)

Core topic

Hard10 min

💡 Concept summary

The `using` and `await using` keywords automatically clean up resources when leaving scope via Symbol.dispose / Symbol.asyncDispose — like Python's "with" and C#'s "using".

🤔 Why do you need this feature?

The resource cleanup pattern (closing a file, releasing a lock, closing a DB connection, aborting a fetch) previously required try/finally — verbose, easy to forget, and chaotic with multiple resources. ES17 introduces the `using x = ...` syntax so the JS engine automatically calls x[Symbol.dispose]() when leaving the block, even on throw. `await using` is for async cleanup.

Before (old style)Verbose / clunky
// Before ES17 — manual try/finally, nested when there are many resources
function readFiles() {
    const file1 = openFile('a.txt');
    try {
        const file2 = openFile('b.txt');
        try {
            const file3 = openFile('c.txt');
            try {
                return processAll(file1, file2, file3);
            } finally {
                file3.close();
            }
        } finally {
            file2.close();
        }
    } finally {
        file1.close();
    }
}

// The pattern is even worse with async (you need to await inside finally)
async function readAsync() {
    const conn = await openDB();
    try {
        const tx = await conn.beginTransaction();
        try {
            return await tx.query('...');
        } finally {
            await tx.rollback();
        }
    } finally {
        await conn.close();
    }
}
ES17 (Modern)Optimal & recommended
// ES17 — using automatically calls dispose when leaving scope
function createResource(name) {
    return {
        name,
        [Symbol.dispose]() {
            console.log(`Closing ${name}`);
        }
    };
}

function readFiles() {
    using file1 = createResource('a.txt');
    using file2 = createResource('b.txt');
    using file3 = createResource('c.txt');
    return process(file1, file2, file3);
    // → automatically calls dispose in reverse order: file3, file2, file1
    // → even on throw, dispose still runs
}

// async version with Symbol.asyncDispose
function createAsyncResource(name) {
    return {
        name,
        async [Symbol.asyncDispose]() {
            await new Promise(r => setTimeout(r, 10));
            console.log(`Async closed ${name}`);
        }
    };
}

async function readAsync() {
    await using conn = createAsyncResource('DB');
    await using tx = createAsyncResource('TX');
    // ... use them
    // → await dispose in LIFO order when leaving the async function
}
Got it down? Try running real code or take the quiz!

ES17 Quick Reference

Quick syntax overview + copy snippets

Explicit Resource Management (using)

The `using` and `await using` keywords automatically clean up resources when leaving scope via Symbol.dispose / Symbol.asyncDispose — like Python's "with" and C#'s "using".

function createResource(name) {
    return {
        name,
...
Error.isError()

A static method that robustly checks whether a value is an Error instance — it works across realms (iframe, worker, structuredClone), which `instanceof Error` cannot handle.

console.log(Error.isError(new Error("ok")));        // true
console.log(Error.isError(new TypeError("ok")));    // true
console.log(Error.isError(new RangeError("ok")));   // true
...
RegExp Pattern Modifiers

The `(?i:abc)` and `(?-i:abc)` syntax turns flags (i, m, s) on/off only within part of a regex instead of the whole thing — reducing complexity when writing a regex that combines multiple case sensitivities.

const pattern = /[A-Z]+(?i:end)/;
console.log(pattern.test("HELLOend"));  // true
console.log(pattern.test("HELLOEND"));  // true
...
Iterator.range() & Iterator.zip()

Two built-in static methods for creating iterators: range(start, end, step) generates a range of numbers, zip(...iters) joins multiple iterators in parallel — no need for Array.from or manual loops.

for (const i of Iterator.range(1, 6)) {
    console.log(i); // 1, 2, 3, 4, 5
}
...