demo · v134 · javascript
SuppressedError
When a body throws AND the disposer also throws, classic try/finally loses the body error. The new using spec introduces SuppressedError — both errors are preserved, with the disposer's error nested inside.
scenario A: body throws, dispose also throws
{
using x = {
[Symbol.dispose]() { throw new Error("dispose failed"); },
};
throw new Error("body failed");
}
// caught: SuppressedError
// .error = Error("dispose failed")
// .suppressed = Error("body failed")
scenario B: only body throws
{
using x = { [Symbol.dispose]() { console.log("disposed cleanly"); } };
throw new Error("body failed");
}
// caught: Error("body failed") — dispose still ran
scenario C: only dispose throws
{
using x = { [Symbol.dispose]() { throw new Error("dispose failed"); } };
// body fine
}
// caught: Error("dispose failed")
scenario D: chain of disposers
{
using a = { [Symbol.dispose]() { throw new Error("a"); } };
using b = { [Symbol.dispose]() { throw new Error("b"); } };
using c = { [Symbol.dispose]() { throw new Error("c"); } };
}
// caught: SuppressedError(SuppressedError(c, b), a) — LIFO chain
why this exists
Before: try { throw new Error("body"); } finally { throw new Error("close"); } — only the close error reaches the catch. The body error is silently dropped. Real-world bug class: a database transaction throws, the rollback also throws, and the operator gets a generic "rollback failed" log with no idea why the transaction was in trouble to begin with.
With using: the spec defines SuppressedError as a structured exception type. The error chain is preserved through arbitrary depth. Tooling (and humans) can walk .suppressed all the way down.