--- title: "GenericTryCatch" slug: "generictrycatch" updated: 2026-08-26T07:36:49Z published: 2026-08-26T07:36:49Z canonical: "docs.viewlift.com/generictrycatch" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.viewlift.com/llms.txt > Use this file to discover all available pages before exploring further. # GenericTryCatch ## GenericTryCatch `GenericTryCatch` is a structured error boundary for async operations — a single static method that wraps try/catch/finally with loading state, error handling, and fallback semantics. It complements `AsyncHandler` (which normalizes errors at API boundaries) by providing the same discipline for UI-initiated async work like data fetching, form submissions, and side effects. ## API ``` import { GenericTryCatch } from '@/lib/internal/GenericTryCatch'; // Void variant — no return value, re-throws on error await GenericTryCatch.run({ run: async () => { await doSomething(); }, setLoading, onError: (err) => showToast(err.message), onFinally: () => cleanup(), }); // Value variant — returns T on success, fallback on error const result = await GenericTryCatch.run({ run: async () => { return await fetchData(); }, fallback: [], setLoading, onError: (err) => showToast(err.message), }); ``` ## Method Signature ``` // Void — no fallback, re-throws normalized error static async run(options: GenericTryCatchVoidOptions): Promise; // Value — fallback provided, swallows error and returns fallback static async run(options: GenericTryCatchValueOptions): Promise; ``` The presence or absence of `fallback` determines the overload: | `fallback` provided? | On success | On error | Return type | | --- | --- | --- | --- | | No | Returns `void` | Calls `onError`, then re-throws | `Promise<void>` | | Yes | Returns `T` | Calls `onError`, returns fallback | `Promise<T>` | ## Options ### Base Options (shared) | Option | Type | Required | Description | | --- | --- | --- | --- | | `setLoading` | `(loading: boolean) => void` | No | Called with `true` before run, `false` in finally | | `onError` | `(error: Error) => void` | No | Called with the normalized error | | `onFinally` | `() => void \| Promise<void>` | No | Called in finally after `setLoading(false)` | ### Void Options | Option | Type | Required | Description | | --- | --- | --- | --- | | `run` | `() => Promise<void>` | Yes | The async operation to execute | ### Value Options | Option | Type | Required | Description | | --- | --- | --- | --- | | `run` | `() => Promise<T>` | Yes | The async operation to execute | | `fallback` | `T \| (() => T)` | Yes | Static value or factory returned on error | ## Execution Flow ![GenericTryCatch-08-26-2026_01_04_PM (1).png](https://cdn.document360.io/15e0c142-c823-4c77-a8e8-9d32ca937982/Images/Documentation/GenericTryCatch-08-26-2026_01_04_PM%20(1).png) ## Error Normalization `GenericTryCatch` normalizes unknown errors internally before passing them to `onError`: ``` function normalizeError(err: unknown): Error { if (err instanceof Error) return err; return new Error(typeof err === 'string' ? err : 'Unknown error'); } ``` This is a lightweight normalization — it produces `Error` instances, not `AppError`. When the `run` function already throws `AppError` (e.g., from services that use `AsyncHandler`), the `AppError` passes through as-is since it extends `Error`. ## Fallback Resolution Fallbacks can be static values or factories: ``` // Static fallback fallback: []; // Factory fallback — called lazily on error fallback: () => getDefaultConfig(); ``` Factories are useful when the default value is expensive to construct or should be created fresh on each error. ## Usage in Practice The auth hook uses `GenericTryCatch` for operations that return data: ``` const refreshTenantList = useCallback(async () => { return await GenericTryCatch.run({ setLoading, onError: (err) => onMessage(err.message, { variant: 'error' }), fallback: [], run: async () => { const deviceId = CookieManager.get('deviceId'); if (!deviceId) throw AppError.create('auth.device_id_missing'); const res = await IdentityService.refreshTenantList(deviceId); return res.tenants; }, }); }, [commonOptions, onMessage]); ``` On success, the caller gets `res.tenants`. On failure, the error is shown as a toast and the caller gets `[]` — no unhandled promise rejection, no crashed UI. ## Relationship to Other Layers ### AsyncHandler - `AsyncHandler` wraps service and API boundaries — ensures only `AppError` escapes - `GenericTryCatch` wraps UI-initiated async work — provides loading/error/fallback ergonomics They complement each other: a service call wrapped by `AsyncHandler` may throw `AppError`, which `GenericTryCatch` catches and routes to `onError`. ### AppError - `GenericTryCatch` does not depend on `AppError` directly - It normalizes `unknown` → `Error`, which preserves `AppError` instances (since `AppError` extends `Error`) - Callers inside `run` are free to throw `AppError` — it flows through correctly ## Design Decisions - **Single static method** — avoids class instantiation; the options object is the entire configuration surface - **Overloaded by fallback presence** — the type system enforces that void operations cannot accidentally swallow errors; if you want error recovery, you must provide a fallback - **`setLoading` in both try and finally** — guarantees loading state is always cleaned up, even on error - **Lightweight normalization** — converts `unknown` to `Error` without requiring `AppError`, keeping `GenericTryCatch` usable outside of service boundaries - **Lazy fallback factories** — `fallback: () => T` defers construction to error time, avoiding unnecessary allocation on the happy path