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.
- Look for hand-written
-
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
PromiseLikevalue in hand.
- Do not wrap APIs that already return
ResultorResultAsync.- Compose them directly with
map,mapErr,andThen,asyncAndThen, ororElse.
- Compose them directly with
Choose the Wrapper
-
Use
Result.fromThrowableorfromThrowablefor synchronous throwing functions.- Always pass an error mapper so the
Errside has a known type.
- Always pass an error mapper so the
-
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.
- Prefer this over
-
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.
- Prefer stable domain errors over
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.
- Examples include cleanup flows that need
-
If
try/catchremains necessary, keep it at the narrowest boundary.- Convert the caught value into
Error the required framework-native response immediately. - Do not let the caught value flow through the codebase as untyped
unknown.
- Convert the caught value into
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
ResultAsyncmanually withPromise.resolve,Promise.reject, or custom wrapper objects.
- Do not simulate
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.