A codebase where some async functions use try/catch, others use .catch(), some use a shared wrapper, and some have no error handling at all makes every error handling review a full context-reload. CWE-755 (Improper Handling of Exceptional Conditions) applies when inconsistency means individual developers add error handling differently, creating gaps that are hard to detect in review. ISO 25010 maintainability.consistency classifies ad hoc error handling as a direct maintainability defect. Practically, a boilerplate-heavy pattern copy-pasted across 15 server actions means adding cross-cutting behavior — like logging or monitoring calls — requires editing every location individually rather than once in a shared utility.
Low because inconsistent async error handling is a code quality and maintainability defect rather than an immediate security or reliability risk, but it degrades both over time.
Create a shared tryCatch utility in src/lib/try-catch.ts and standardize server actions and API helpers on it.
// src/lib/try-catch.ts
export async function tryCatch<T>(
fn: () => Promise<T>
): Promise<[T, null] | [null, Error]> {
try {
const result = await fn()
return [result, null]
} catch (error) {
return [null, error instanceof Error ? error : new Error(String(error))]
}
}
// Usage in a server action:
const [user, error] = await tryCatch(() => db.users.findUnique({ where: { id } }))
if (error) { return { error: 'User not found' } }
Once a shared utility is in place, adding cross-cutting logging or monitoring to all async calls requires a single edit in one file rather than hunting across the codebase.
ID: saas-error-handling.graceful-degradation.async-error-handling-consistent
Severity: low
What to look for: Survey the async error handling patterns across the codebase. Look for: (1) Are most async functions using try/catch, or .catch(), or a mix? (2) Is there a shared error handling utility (e.g., a tryCatch() wrapper, a withErrorHandling() HOF, or a Result type pattern) used consistently, or is error handling written ad hoc in every file? (3) In Next.js: are server actions consistently wrapped with error handling, or is each server action a different pattern? (4) Are there helper files that centralize error handling logic, or is the same boilerplate duplicated everywhere?
Pass criteria: Enumerate all async error handling patterns used in the project (try/catch, .catch(), Result type, shared wrapper). Pass if at least 80% of async functions use the same error handling pattern — either all try/catch, a shared wrapper utility, or a Result/Either pattern used project-wide. Consistency is the signal; the specific pattern matters less. Report the ratio: "X of Y async functions follow the dominant error handling pattern."
Fail criteria: Fail if error handling is ad hoc and inconsistent — some functions use try/catch, others use .catch(), others have no error handling, with no apparent deliberate pattern. Fail if the same error handling boilerplate is copy-pasted across 10+ locations rather than centralized.
Skip (N/A) when: The project is small enough (under 10 async functions total) that consistency is moot — each can be individually inspected.
Detail on fail: Describe the inconsistency (e.g., "Mix of try/catch and .catch() patterns; 12 server actions have 6 different error handling approaches; no shared error utility found"). Max 500 chars.
Remediation: Inconsistent error handling means each developer adds error handling differently, making it hard to review, audit, or improve systematically. A shared utility makes the right pattern easy and the wrong pattern obvious.
A simple try/catch wrapper that works well for server actions and API helpers:
// lib/try-catch.ts
export async function tryCatch<T>(
fn: () => Promise<T>
): Promise<[T, null] | [null, Error]> {
try {
const result = await fn()
return [result, null]
} catch (error) {
return [null, error instanceof Error ? error : new Error(String(error))]
}
}
// Usage in server action:
const [result, error] = await tryCatch(() => db.users.findUnique({ where: { id } }))
if (error) { return { error: 'User not found' } }
Standardizing on one pattern also makes it easier to add cross-cutting concerns (logging, monitoring) in a single place later.