@exadel/esl
Version:
Exadel Smart Library (ESL) is the lightweight custom elements library that provide a set of super-flexible components
146 lines (123 loc) • 4.81 kB
Markdown
# Decorator
Lightweight resilience decorator for methods and getters. It wraps the original implementation in a synchronous `try/catch` and returns a predefined (or lazily provided) fallback value instead of throwing, while optionally delegating the error to a centralized logger hook `$$error`.
---
## Why
In UI component code a lot of accessors and small helper methods may occasionally fail (uninitialized state, missing DOM, parsing issues) where throwing an exception is undesirable and a neutral fallback value suffices. Repeating local `try/catch` blocks:
- Adds noise / decreases readability.
- Is easy to forget.
- Causes inconsistent logging.
`` offers:
- Tiny implementation (no deps, minimal runtime overhead).
- Consistent optional logging via `this.$$error`.
- Declarative fallback semantics (static value or lazy provider).
---
## Quick Start
```ts
import {safe} from '@exadel/esl/modules/esl-utils/decorators/safe';
class ProfileCard {
data?: {name: string};
// Static fallback value
get name(): string {
if (!this.data) throw new Error('No data');
return this.data.name;
}
// Lazy fallback provider (executed only when an error occurs)
collectTags(): string[] {
// may throw synchronously
return computeTags(this.data!);
}
// Optional centralized error logger (receives the thrown error, method name and original fn reference)
$$error(err: unknown, name: string, original: Function) {
console.error('[safe]', name, err);
}
}
```
---
## API
```ts
function safe<Fallback = null>(fallback?: Fallback | (() => Fallback)): MethodDecorator;
```
Applied to:
- Class methods
- Get accessors
Resulting wrapped signature:
```
Original: (...args) => R
Wrapped : (...args) => R | Fallback
```
(Getters: `() => T` becomes `() => T | Fallback`)
### Parameters
| Name | Type | Default | Description |
|------|------|---------|-------------|
| `fallback` | `Fallback` \| `() => Fallback` | `null` | Static value or provider invoked with instance context (`this`) only when an error is caught. |
### Logger Hook
If an instance defines:
```ts
$$error(error: unknown, methodName: string, original: Function): void
```
it is invoked before returning the fallback. Rethrow inside `$$error` if you need to abort.
---
## Fallback Semantics
1. Call proceeds normally: original return value is passed through.
2. Synchronous throw occurs: `fallback` is resolved:
- If function: invoked with `this` context (no arguments).
- Else: used as-is.
3. Return the resolved fallback; no rethrow (unless you rethrow inside `$$error`).
Note: Asynchronous errors (Promise rejections) are NOT intercepted unless they are thrown before the first `await` (i.e. still synchronous part of the function body).
---
## Examples
### Getter with static fallback
```ts
get title(): string {
if (!this.source) throw new Error('missing');
return this.source.title;
}
```
### Method with lazy fallback
```ts
items(): Item[] {
// may throw (invalid cache, parse error, etc.)
return this.cache!.list();
}
```
### Combine with other decorators
`` can stack with memoization, binding, etc. (Order matters with decorators that wrap execution.)
```ts
class Example {
computeHeavy(): number {
if (!this.ready) throw new Error('not ready');
return heavyCalc();
}
}
```
(Here `` runs inside the memoized wrapper because it is applied after ``.)
### Centralized Logger
```ts
$$error(err: unknown, name: string) {
sendToTelemetry({component: this.baseTagName, name, message: String(err)});
}
```
---
## Limitations
| Aspect | Status | Rationale |
|--------|--------|-----------|
| Async rejections | Not caught | Keeps wrapper lean; use local try/catch inside async functions. |
| Error-aware fallback (receives error) | Not supported | Avoids extra function shape / overhead; consider manual try/catch if needed. |
| Setter decoration | Not supported | Current scope is methods & getters only. |
| Type narrowing after fallback | Not attempted | Simplicity; consumer can refine manually. |
---
## Typing Notes
The returned type is a union: `OriginalReturn | FallbackType`.
If you provide `` TypeScript still widens to `R | undefined`. Choose explicit neutral sentinels where clarity matters.
---
## Best Practices
- Keep fallbacks simple and side-effect free.
- Use lazy providers only for non-trivial allocations (arrays, objects) to avoid paying the cost on success path.
- Log selectively: high‑volume silent recoveries may be normal; consider sampling or suppression logic inside `$$error`.
- Don’t mask programming errors you truly care about—rethrow inside `$$error` when appropriate (e.g. in development builds).