# @helpers4/promise

> Tree-shakable TypeScript utility functions for the `promise` domain.
> Package: `@helpers4/promise` — Version: 2.0.0
> License: LGPL-3.0-or-later

## Installation

```sh
npm install @helpers4/promise
# or
pnpm add @helpers4/promise
```

## Usage

```typescript
import { consoleLogPromise, defer, delay, ... } from '@helpers4/promise';
```

## Functions

| Function | Description |
|---|---|
| `consoleLogPromise` | Returns a function that logs data to the console and passes it through. |
| `defer` | Runs an async function and guarantees that all deferred callbacks are executed afterwards, in LIFO o |
| `delay` | Creates a promise that resolves after specified delay |
| `falsyPromiseOrThrow` | Returns a function that passes through falsy data or throws an error. |
| `guard` | Wraps a function so that if it throws, a default value is returned instead of propagating the error. |
| `meaningPromiseOrThrow` | Returns a function that passes through meaningful data or throws an error. Data is considered meanin |
| `parallel` | Runs an array of async functions with a concurrency limit. At most `limit` functions will be running |
| `resolveRecord` | Resolves an array of keys into a record by calling an async mapper for each key. All mapper calls ru |
| `retry` | Retries a promise-returning function up to maxAttempts times |
| `safeFetch` | Wraps `fetch` with built-in error handling: returns `null` when the request fails (network error, no |
| `timeout` | Wraps a promise to reject with a `TimeoutError` if it does not resolve within the specified duration |
| `truthyPromiseOrThrow` | Returns a function that passes through truthy data or throws an error. |
| `tryit` | Wraps a function so it never throws. Instead, it returns a `[error, result]` tuple. Useful for avoid |

---

## API Reference

### `consoleLogPromise`

Returns a function that logs data to the console and passes it through.

```typescript
import { consoleLogPromise } from '@helpers4/promise';

consoleLogPromise<T>(prefix?: string): function
```

**Parameters:**

- `prefix?: string` — Optional prefix for the console log

**Returns:** `function` — A function that logs and returns the data

**Examples:**

*Log and pass-through in a promise chain*

Creates a function that logs data with an optional prefix and returns it unchanged.

```typescript
Promise.resolve(42).then(consoleLogPromise('value:'))
// logs "value: 42" and resolves with 42
```

---

### `defer`

Runs an async function and guarantees that all deferred callbacks are
executed afterwards, in LIFO order (last registered = first executed),
regardless of whether the main work succeeds or throws.

Inspired by Radashi's `defer`. Useful for resource cleanup, temporary file
removal, or any "undo" logic that must run even on failure.

```typescript
import { defer } from '@helpers4/promise';

defer<T>(fn: function): Promise<T>
```

**Parameters:**

- `fn: function` — An async function that receives a `defer` registration function.

**Returns:** `Promise<T>` — The resolved value of `fn`.

**Examples:**

*Cleanup always runs*

Registered callbacks execute after the main function, even on success.

```typescript
const result = await defer(async (d) => {
  d(() => console.log('cleanup'));
  return 42;
});
// logs: 'cleanup' — result is 42
```

*LIFO order*

Multiple callbacks are called in reverse registration order.

```typescript
await defer(async (d) => {
  d(() => console.log('step 1'));
  d(() => console.log('step 2'));
  d(() => console.log('step 3'));
});
// logs: 'step 3', 'step 2', 'step 1'
```

*Cleanup runs even on failure*

Callbacks still execute when the main function throws; the error is re-thrown after.

```typescript
const releaseLock = () => console.log('lock released');
await defer(async (d) => {
  d(releaseLock);
  throw new Error('something failed');
}).catch(() => {});
// logs: 'lock released'
```

---

### `delay`

Creates a promise that resolves after specified delay

```typescript
import { delay } from '@helpers4/promise';

delay<T = void>(ms: number, value?: T): Promise<T>
```

**Parameters:**

- `ms: number` — Milliseconds to delay
- `value?: T` — Optional value to resolve with

**Returns:** `Promise<T>` — Promise that resolves after delay

**Examples:**

*Wait a specified duration*

Creates a promise that resolves after the given milliseconds.

```typescript
await delay(100)
// resolves after 100ms
```

*Resolve with a value*

Optionally resolves with a provided value.

```typescript
const result = await delay(100, 'done')
// => 'done'
```

---

### `falsyPromiseOrThrow`

Returns a function that passes through falsy data or throws an error.

```typescript
import { falsyPromiseOrThrow } from '@helpers4/promise';

falsyPromiseOrThrow<T>(error: string): function
```

**Parameters:**

- `error: string` — The error message to throw if data is truthy

**Returns:** `function` — A function that returns the data if falsy, or throws

**Examples:**

*Pass through falsy values*

Returns the value if falsy, throws otherwise.

```typescript
Promise.resolve(null).then(falsyPromiseOrThrow('Expected falsy'))
// => null
```

*Throw on truthy values*

Throws an error when the value is truthy.

```typescript
Promise.resolve('oops').then(falsyPromiseOrThrow('Should be empty'))
// throws Error('Should be empty')
```

---

### `guard`

Wraps a function so that if it throws, a default value is returned instead of propagating the error.
Works with both synchronous and asynchronous functions.

```typescript
import { guard } from '@helpers4/promise';

guard<T>(fn: function, defaultValue: T): Promise<T>
```

**Parameters:**

- `fn: function` — The function to guard
- `defaultValue: T` — The value to return if the function throws

**Returns:** `Promise<T>` — The result of the function, or the default value on error

```typescript
import { guard } from '@helpers4/promise';

guard<T>(fn: function, defaultValue: T): T
```

**Parameters:**

- `fn: function` — The function to guard
- `defaultValue: T` — The value to return if the function throws

**Returns:** `T` — The result of the function, or the default value on error

**Examples:**

*Fallback on parse error*

Returns a default value when the function throws.

```typescript
const result = guard(() => JSON.parse('invalid'), {})
// => {}
```

*Pass-through on success*

Returns the function result when it does not throw.

```typescript
const result = guard(() => JSON.parse('{"a":1}'), {})
// => { a: 1 }
```

---

### `meaningPromiseOrThrow`

Returns a function that passes through meaningful data or throws an error.
Data is considered meaningless if it is null, undefined, empty string, empty object, or empty array.

```typescript
import { meaningPromiseOrThrow } from '@helpers4/promise';

meaningPromiseOrThrow<T>(error: string): function
```

**Parameters:**

- `error: string` — The error message to throw if data is meaningless

**Returns:** `function` — A function that returns the data if meaningful, or throws

**Examples:**

*Pass through meaningful values*

Returns the value if it is not empty (null, undefined, empty string, empty object, empty array).

```typescript
Promise.resolve({ key: 'value' }).then(meaningPromiseOrThrow('No data'))
// => { key: 'value' }
```

*Throw on empty values*

Throws when the value is null, undefined, empty string, empty object, or empty array.

```typescript
Promise.resolve({}).then(meaningPromiseOrThrow('Empty!'))
// throws Error('Empty!')
```

---

### `parallel`

Runs an array of async functions with a concurrency limit.
At most `limit` functions will be running at any time.

```typescript
import { parallel } from '@helpers4/promise';

parallel<T>(functions: readonly function[], limit: number): Promise<T[]>
```

**Parameters:**

- `functions: readonly function[]` — Array of functions that return promises
- `limit: number` — Maximum number of concurrent executions

**Returns:** `Promise<T[]>` — Promise that resolves with an array of results in the same order as the input

**Examples:**

*Run tasks with concurrency limit*

Executes async functions with at most N running concurrently.

```typescript
const results = await parallel(
  [() => fetch('/a'), () => fetch('/b'), () => fetch('/c')],
  2
)
// At most 2 requests run at a time; results are in order
```

*Sequential execution with limit of 1*

Setting limit to 1 runs functions one at a time.

```typescript
await parallel([fnA, fnB, fnC], 1)
// Runs fnA, then fnB, then fnC
```

---

### `resolveRecord`

Resolves an array of keys into a record by calling an async mapper for each key.
All mapper calls run concurrently via `Promise.all`.

Unlike parallel, which returns an array, `resolveRecord` preserves the
key-to-value relationship in the result.

```typescript
import { resolveRecord } from '@helpers4/promise';

resolveRecord<K extends PropertyKey, V>(keys: readonly K[], mapper: function): Promise<Record<K, V>>
```

**Parameters:**

- `keys: readonly K[]` — The keys to resolve
- `mapper: function` — Async function called for each key, returning the associated value

**Returns:** `Promise<Record<K, V>>` — A record mapping each key to its resolved value

**Examples:**

*Fetch data for multiple keys concurrently*

All mapper calls run in parallel via Promise.all.

```typescript
const stars = await resolveRecord(
  ['helpers4/typescript', 'helpers4/devcontainer'],
  async (repo) => fetchRepoStars(repo)
);
// => { 'helpers4/typescript': 42, 'helpers4/devcontainer': 17 }
```

---

### `retry`

Retries a promise-returning function up to maxAttempts times

```typescript
import { retry } from '@helpers4/promise';

retry<T>(fn: function, maxAttempts: number, delayMs: number): Promise<T>
```

**Parameters:**

- `fn: function` — The function to retry
- `maxAttempts: number` (default: `3`) — Maximum number of attempts
- `delayMs: number` (default: `1000`) — Delay between attempts in milliseconds

**Returns:** `Promise<T>` — Promise that resolves with the result or rejects with the last error

**Examples:**

*Retry a failing function*

Retries the function up to maxAttempts times before giving up.

```typescript
let attempt = 0;
await retry(() => {
  attempt++;
  if (attempt < 3) throw new Error('not yet');
  return Promise.resolve('success');
}, 3, 10)
// => 'success' (after 2 failures)
```

---

### `safeFetch`

Wraps `fetch` with built-in error handling: returns `null` when the
request fails (network error, non-OK status, or parse error) instead
of throwing.

```typescript
import { safeFetch } from '@helpers4/promise';

safeFetch<T>(input: RequestInfo | URL, init?: RequestInit, options: SafeFetchOptions): Promise<T | null>
```

**Parameters:**

- `input: RequestInfo | URL` — URL or `Request` object passed to `fetch`
- `init?: RequestInit` — Optional `RequestInit` options passed to `fetch`
- `options: SafeFetchOptions` (default: `{}`) — Parsing options (default: `{ parse: 'json' }`)

**Returns:** `Promise<T | null>` — The parsed response body, or `null` on any failure

**Examples:**

*Fetch JSON safely*

Returns `null` on network error or non-OK status instead of throwing.

```typescript
const repo = await safeFetch<{ stars: number }>(
  'https://api.github.com/repos/helpers4/typescript'
);
if (repo === null) {
  console.warn('Failed to fetch repo data');
} else {
  console.log(repo.stars);
}
```

*Fetch plain text*

Pass { parse: "text" } to get the raw response body as a string.

```typescript
const content = await safeFetch<string>(
  'https://example.com/data.txt',
  undefined,
  { parse: 'text' }
);
```

---

### `timeout`

Wraps a promise to reject with a `TimeoutError` if it does not resolve within the specified duration.

```typescript
import { timeout } from '@helpers4/promise';

timeout<T>(promise: Promise<T>, ms: number): Promise<T>
```

**Parameters:**

- `promise: Promise<T>` — The promise to wrap
- `ms: number` — Timeout duration in milliseconds

**Returns:** `Promise<T>` — A promise that rejects with `TimeoutError` if the timeout is exceeded

**Examples:**

*Reject a slow promise*

Throws a TimeoutError if the promise does not resolve in time.

```typescript
await timeout(fetch('/api/data'), 5000)
// Rejects with TimeoutError if fetch takes longer than 5s
```

*Resolve fast promise normally*

Returns the value if the promise resolves before the timeout.

```typescript
const result = await timeout(Promise.resolve('fast'), 1000)
// => 'fast'
```

---

### `truthyPromiseOrThrow`

Returns a function that passes through truthy data or throws an error.

```typescript
import { truthyPromiseOrThrow } from '@helpers4/promise';

truthyPromiseOrThrow<T>(error: string): function
```

**Parameters:**

- `error: string` — The error message to throw if data is falsy

**Returns:** `function` — A function that returns the data if truthy, or throws

**Examples:**

*Pass through truthy values*

Returns the value if truthy, throws otherwise.

```typescript
Promise.resolve('data').then(truthyPromiseOrThrow('No data'))
// => 'data'
```

*Throw on falsy values*

Throws an error when the value is falsy.

```typescript
Promise.resolve('').then(truthyPromiseOrThrow('Empty!'))
// throws Error('Empty!')
```

---

### `tryit`

Wraps a function so it never throws. Instead, it returns a `[error, result]` tuple.
Useful for avoiding try/catch blocks and handling errors in a functional style.

```typescript
import { tryit } from '@helpers4/promise';

tryit<TArgs extends readonly unknown[], TReturn>(fn: function): function
```

**Parameters:**

- `fn: function` — The function to wrap (sync or async)

**Returns:** `function` — A new function that returns a `Result` tuple

**Examples:**

*Safe JSON parsing*

Wraps JSON.parse to return a tuple instead of throwing.

```typescript
const safeParse = tryit(JSON.parse);
const [error, data] = safeParse('{"a":1}');
// error === undefined, data === { a: 1 }
```

*Catching errors without try/catch*

On error, the first element of the tuple is the Error.

```typescript
const safeParse = tryit(JSON.parse);
const [error, data] = safeParse('invalid');
// error instanceof SyntaxError, data === undefined
```

---
