Neverthrow Exception Wrapping
Goal
Capture recoverable exceptions with neverthrow helpers instead of ad hoc try/catch.
This skill governs exception capture only. If the task also changes public return signatures, use neverthrow-return-types alongside this skill.
Detect Exception Sources
- Identify where failures currently enter the code.
- Look for hand-written try/catch, .catch(...) wrappers used only for conversion, direct calls to known throwing APIs, and promise-returning functions that may reject. - Check third-party libraries, parsers, database clients, network clients, file-system helpers, schema validators, and serialization code.
- Distinguish the failure shape before choosing a wrapper.
- Use the synchronous path when the operation may throw before returning a value. - Use the promise-function path when the operation returns a promise but may still throw before that promise exists. - Use the promise-instance path when you already have a PromiseLike value in hand.
- Do not wrap APIs that already return
ResultorResultAsync.
- Compose them directly with map, mapErr, andThen, asyncAndThen, or orElse.
Choose the Wrapper
- Use
Result.fromThrowableorfromThrowablefor synchronous throwing functions.
- Always pass an error mapper so the Err side has a known type.
- Use
ResultAsync.fromThrowablefor promise-returning functions that can throw before returning or fail during async execution.
- Prefer this over ResultAsync.fromPromise(fn(...),...) when the function call itself might throw.
- Use
ResultAsync.fromPromiseorfromPromisewhen you already have aPromiseLikevalue.
- Map rejected values into a concrete error type immediately.
- Reuse narrow mapper functions when the same error shape appears repeatedly.
- Prefer stable domain errors over unknown, any, and generic strings.
Avoid try/catch by Default
- Do not add new hand-written
try/catchblocks when aneverthrowhelper fits the job.
- Extract the risky operation into a function if needed and wrap that function.
- Keep
try/catchonly when the surrounding construct truly requires it.
- Examples include cleanup flows that need finally, framework boundaries that must intercept and translate exceptions, or language constructs that cannot be expressed cleanly with wrapper helpers alone.
- If
try/catchremains necessary, keep it at the narrowest boundary.
- Convert the caught value into Err or the required framework-native response immediately. - Do not let the caught value flow through the codebase as untyped unknown.
Implementation Rules
- Wrap once near the source of the throwable or rejecting operation.
- Avoid nested wrappers around the same operation.
- Keep error mapping explicit.
- Prefer mapper functions that preserve useful context such as operation name, input identifiers, or upstream status codes when the local style allows it.
- Replace conversion-only
.catch(...)chains whenneverthrowprovides a clearer wrapper.
- Do not simulate ResultAsync manually with Promise.resolve, Promise.reject, or custom wrapper objects.
Example Patterns
const parseConfig = Result.fromThrowable(
JSON.parse,
(error) => ({ type: 'ConfigParseError', cause: error }),
)
const fetchUser = ResultAsync.fromThrowable(
apiClient.getUser,
(error) => ({ type: 'UserFetchError', cause: error }),
)
function readBody(): ResultAsync<RequestBody, BodyReadError> {
return ResultAsync.fromPromise(request.json(), toBodyReadError)
}Validate Before Finishing
- Verify new or edited failure capture uses
neverthrowhelpers where applicable. - Verify each wrapper choice matches the real failure shape: synchronous throw, promise-returning function, or existing promise.
- Verify all error mappers produce explicit error types.
- Verify any remaining
try/catchblock is documented by a real constraint instead of habit. - Run the normal local validation for the stack when it is safe and in scope, such as tests, linting, or type checks.
Report the Outcome
When finishing the task:
- State which throwing or rejecting operations were wrapped.
- State which
neverthrowhelper was used and why. - State any remaining
try/catchblocks and why they were unavoidable. - State how caught or rejected values are mapped into explicit error types.