A dependable boundary distinguishes invalid user input from a broken program. Throwing an exception interrupts normal execution and transfers control to an enclosing catch. The catch should handle failures it understands. Turning every failure into an ordinary empty result can make a programming defect look like valid missing data.
function positive(value) {
if (!Number.isInteger(value) || value <= 0) throw new RangeError("expected a positive integer");
return value;
}
try {
console.log(positive(0));
} catch (error) {
console.log(error.name);
}Choose error classes as part of the interface. In this lesson, a non-string port is a TypeError, while malformed or out-of-range port text is a RangeError. The precise policy belongs to this API; JavaScript does not automatically choose those classes for your validation. Both inherit from Error, but that shared parent is too broad for the recovery wrapper.
Parsing must respect the complete input contract. parseInt("80oops",10) returns 80 because it consumes a numeric prefix. That is useful in some contexts and incorrect for a complete port string. Validate the trimmed spelling first, convert it, then check the numeric bounds. The inclusive minimum and maximum deserve their own tests because an accidental strict comparison can reject a legitimate boundary.
Expected failures can become structured data at the application's outer boundary. tryParsePort returns either an explicit success value or a stable error description. Its injectable parser demonstrates the difference between a validation failure and an unexpected dependency error. Call that dependency once; catching an error should not trigger a second attempt unless retries are explicitly part of the contract.
function attempt(parse) {
try { return {ok: true, value: parse()}; }
catch (error) {
if (error instanceof RangeError) return {ok: false, error: "invalid input"};
throw error;
}
}
console.log(JSON.stringify(attempt(() => { throw new RangeError("detail"); })));Rethrowing the same unexpected error preserves its identity and stack. A caller can attach context or let the process fail visibly. Logging inside a low-level parser would duplicate diagnostics and couple reusable code to a particular interface. Return structured results from the boundary and let the command-line wrapper decide which stream to use later.
Use finally for cleanup, with care: it runs during both normal and exceptional exit. A return or throw inside finally can override an earlier result or failure. The output checks include an intentionally surprising example so you can recognize it, not copy it into validation code.
For practice, list a type error, a spelling error, a range error and an unrelated dependency error. Predict which ones become {ok:false} and which one escapes. Then assert that the escaping object is exactly the original error. A useful recovery policy states both what it can repair and what evidence it must preserve.