unknown-error
Turn whatever a catch receives into a real Error, and print its whole cause chain, without ever throwing.
ts
import { formatError, getErrorMessage, hasCode, toError } from 'unknown-error';
try {
await readConfig();
} catch (err) {
if (hasCode(err, 'ENOENT')) {
console.log(err.code); }
console.error(formatError(err));
throw new Error(`Failed to read config: ${getErrorMessage(err)}`, { cause: toError(err) });
}Features
- Never throws: Safe on Proxies that throw from every trap, throwing getters, and
toJSONortoStringthat throw. Handling an error can't raise a new one inside yourcatch. - Any realm, any runtime: Recognizes Errors from
vm, iframes and workers, whereinstanceof Errorfails. Runs in Node.js, Deno, Bun and browsers, usingError.isErrorwhere it exists. - Keeps what it can: Rebuilds error-shaped objects (from JSON, RPC or
postMessage) with their name, stack, cause andcode. Describes anything else readably, including circular objects and BigInts. See Converting caught values. - Whole error trees: Walks
causechains andAggregateErrorentries without recursion, so circular or 100,000-deep chains end instead of overflowing the stack. See Cause chains. - Doesn't modify your errors: Errors are returned as they are, never patched in place.
Compared to
| unknown-error | normalize-exception | ensure-error | serialize-error | pony-cause | |
|---|---|---|---|---|---|
| Never throws on hostile values | ✅ | ✅ | ❌ | ❌ | — |
| Errors from other realms | ✅ | ✅ | ❌ | ✅ | ❌ |
| Readable circular and BigInt values | ✅ | ❌ | ❌ | ❌ | — |
| Leaves your errors unmodified | ✅ | ❌ | ❌ | ✅ | — |
Causes and AggregateError | ✅ | ✅ | ✅ | — | Causes only |
| 10,000-deep cause chains | ✅ | ❌ | ❌ | — | ❌ |
See Why unknown-error for the full comparison.