Overview
UnhandledException wraps exceptions that are caught by Result.try() or Result.tryPromise() when no custom catch handler is provided. It automatically derives a descriptive error message from the caught exception.
This error type represents expected exceptions that are handled gracefully by the Result abstraction, as opposed to Panic which represents unrecoverable defects.
Class Definition
class UnhandledException extends TaggedError ( "UnhandledException" )<{
message : string ;
cause : unknown ;
}>() {
constructor ( args : { cause : unknown }) {
const message =
args . cause instanceof Error
? `Unhandled exception: ${ args . cause . message } `
: `Unhandled exception: ${ String ( args . cause ) } ` ;
super ({ message , cause: args . cause });
}
}
Properties
_tag
'UnhandledException'
required
Discriminator tag, always "UnhandledException".
Automatically derived message:
If cause is an Error: "Unhandled exception: <error.message>"
Otherwise: "Unhandled exception: <String(cause)>"
The original exception that was caught. Can be any value (Error, string, object, etc.).
Error name, set to "UnhandledException".
Stack trace. If cause is an Error, includes “Caused by:” chain with the original stack.
Constructor
new UnhandledException ({ cause: unknown })
The exception that was caught. Can be any value.
Examples
// With Error cause
const err1 = new UnhandledException ({ cause: new Error ( "Network timeout" ) });
console . log ( err1 . message ); // "Unhandled exception: Network timeout"
// With string cause
const err2 = new UnhandledException ({ cause: "Something went wrong" });
console . log ( err2 . message ); // "Unhandled exception: Something went wrong"
// With null cause
const err3 = new UnhandledException ({ cause: null });
console . log ( err3 . message ); // "Unhandled exception: null"
When It’s Created
Result.try()
When Result.try() is called without a custom catch handler:
import { Result } from "better-result" ;
const result = Result . try (() => {
throw new Error ( "Parse failed" );
});
if ( Result . isError ( result )) {
console . log ( result . error ); // UnhandledException
console . log ( result . error . _tag ); // "UnhandledException"
console . log ( result . error . message ); // "Unhandled exception: Parse failed"
console . log ( result . error . cause ); // Error("Parse failed")
}
Result.tryPromise()
When Result.tryPromise() is called without a custom catch handler:
const result = await Result . tryPromise ( async () => {
const response = await fetch ( url );
if ( ! response . ok ) throw new Error ( "HTTP " + response . status );
return response . json ();
});
if ( Result . isError ( result )) {
console . log ( result . error ); // UnhandledException
console . log ( result . error . cause ); // Original Error
}
Usage Patterns
Basic Error Handling
function parseJSON ( text : string ) : Result < unknown , UnhandledException > {
return Result . try (() => JSON . parse ( text ));
}
const result = parseJSON ( '{invalid}' );
if ( Result . isError ( result )) {
console . error ( "Failed to parse JSON:" , result . error . message );
// Log original cause for debugging
console . debug ( "Cause:" , result . error . cause );
}
Type Guard
Check if an error is an UnhandledException:
import { UnhandledException } from "better-result" ;
function handleError ( error : unknown ) {
if ( UnhandledException . is ( error )) {
console . log ( "Caught exception:" , error . cause );
}
}
Accessing the Original Exception
const result = Result . try (() => {
throw new TypeError ( "Expected string" );
});
if ( Result . isError ( result )) {
const cause = result . error . cause ;
if ( cause instanceof TypeError ) {
console . log ( "Type error:" , cause . message );
} else if ( cause instanceof SyntaxError ) {
console . log ( "Syntax error:" , cause . message );
}
}
Pattern Matching
import { matchError , UnhandledException } from "better-result" ;
type AppError = NotFoundError | ValidationError | UnhandledException ;
function handleError ( error : AppError ) : string {
return matchError ( error , {
NotFoundError : ( e ) => `Not found: ${ e . id } ` ,
ValidationError : ( e ) => `Invalid ${ e . field } ` ,
UnhandledException : ( e ) => `Unexpected error: ${ e . message } `
});
}
Avoiding UnhandledException
Use custom catch handlers to convert exceptions into domain-specific errors:
With Result.try()
class ParseError extends TaggedError ( "ParseError" )<{
message : string ;
input : string ;
}>() {}
function parseJSON ( text : string ) : Result < unknown , ParseError > {
return Result . try ({
try : () => JSON . parse ( text ),
catch : ( cause ) => new ParseError ({
message: cause instanceof Error ? cause . message : String ( cause ),
input: text
})
});
}
// Now returns ParseError instead of UnhandledException
const result = parseJSON ( '{invalid}' );
With Result.tryPromise()
class NetworkError extends TaggedError ( "NetworkError" )<{
message : string ;
url : string ;
status ?: number ;
}>() {}
async function fetchData ( url : string ) : Promise < Result < Data , NetworkError >> {
return Result . tryPromise ({
try : async () => {
const res = await fetch ( url );
if ( ! res . ok ) throw new Error ( `HTTP ${ res . status } ` );
return res . json ();
},
catch : ( cause ) => new NetworkError ({
message: cause instanceof Error ? cause . message : String ( cause ),
url
})
});
}
Comparison with Panic
Aspect UnhandledException Panic Created by Result.try / tryPromise without catch handlerUser code throwing inside combinators Represents Expected exception (handled gracefully) Unrecoverable defect (bug) Should catch? ✅ Yes - wrap in Result ❌ No - let it crash When to use Wrapping throwing code you expect to fail Never - should be fixed Stack trace Original exception + wrapper Callback/cleanup location
UnhandledException (Expected)
Panic (Bug)
// This is fine - exception is handled
const result = Result . try (() => {
throw new Error ( "expected failure" );
});
// Returns: Err(UnhandledException)
The message is automatically formatted based on the cause type:
// Error cause
new UnhandledException ({ cause: new Error ( "timeout" ) });
// message: "Unhandled exception: timeout"
// String cause
new UnhandledException ({ cause: "connection failed" });
// message: "Unhandled exception: connection failed"
// Number cause
new UnhandledException ({ cause: 404 });
// message: "Unhandled exception: 404"
// Object cause
new UnhandledException ({ cause: { code: "ERR" } });
// message: "Unhandled exception: [object Object]"
// null cause
new UnhandledException ({ cause: null });
// message: "Unhandled exception: null"
Stack Trace Chaining
When the cause is an Error, stack traces are chained:
const result = Result . try (() => {
const inner = new Error ( "root cause" );
throw inner ;
});
if ( Result . isError ( result )) {
console . log ( result . error . stack );
// UnhandledException: Unhandled exception: root cause
// at ...
// Caused by:
// Error: root cause
// at ...
}
JSON Serialization
const error = new UnhandledException ({
cause: new Error ( "Network timeout" )
});
const json = error . toJSON ();
// {
// _tag: "UnhandledException",
// name: "UnhandledException",
// message: "Unhandled exception: Network timeout",
// cause: {
// name: "Error",
// message: "Network timeout",
// stack: "..."
// },
// stack: "..."
// }
console . log ( JSON . stringify ( error ));
Best Practices
Prefer custom catch handlers for better error types:
// ❌ Generic UnhandledException
Result . try (() => riskyOperation ());
// ✅ Domain-specific error
Result . try ({
try : () => riskyOperation (),
catch : ( e ) => new OperationError ({ cause: e })
});
Use for quick prototyping , refine later:
// Start with this
const result = Result . try (() => JSON . parse ( text ));
// Refine to this
const result = Result . try ({
try : () => JSON . parse ( text ),
catch : ( e ) => new ParseError ({ message: String ( e ), input: text })
});
Log the cause for debugging:
if ( Result . isError ( result ) && UnhandledException . is ( result . error )) {
logger . error ( 'Unhandled exception' , {
message: result . error . message ,
cause: result . error . cause ,
stack: result . error . stack
});
}
Check cause type when handling:
if ( Result . isError ( result )) {
const { cause } = result . error ;
if ( cause instanceof SyntaxError ) {
// Handle syntax errors specially
} else if ( cause instanceof TypeError ) {
// Handle type errors specially
}
}
See Also