nc.func
Function helpers: debounce, throttle, memoize, retry with backoff, timeouts, error handling without try/catch, and typed composition.
const { func } = require("@ix-xs/node-comfort");
import { debounce } from "@ix-xs/node-comfort/func";const save = func.debounce(persist, 300);
const data = await func.retry(() => fetchJSON(url), { attempts: 5, delay: 200, backoff: 2 });
const [error, user] = await func.to(getUser(id));nc.func.Functions
nc.func.debounce()
debounce<F extends (...args: any[]) => any>(fn: F, wait: number, options?: DebounceOptions): DebouncedFunction<F>Waits for a pause of wait ms between calls, then runs once with the latest arguments. For search boxes, resizing, auto-saving...
Parameters
| Name | Type | Description |
|---|---|---|
fn | F | |
wait | number | Pause needed, in ms. |
optionsoptional | DebounceOptions |
Returns DebouncedFunction<F>
Example
const search = func.debounce((query) => api.search(query), 300);
input.on("input", (e) => search(e.target.value));
const save = func.debounce(persist, 1000, { maxWait: 5000 }); // saves at least every 5snc.func.throttle()
throttle<F extends (...args: any[]) => any>(fn: F, wait: number, options?: ThrottleOptions): ThrottledFunction<F>Runs at most once every wait ms, however often it's called. For scroll handlers, progress reports, rate-limited APIs...
Parameters
| Name | Type | Description |
|---|---|---|
fn | F | |
wait | number | Minimum gap between calls, in ms. |
optionsoptional | ThrottleOptions |
Returns ThrottledFunction<F>
Example
const report = func.throttle((pct) => console.log(`${pct}%`), 1000);nc.func.once()
once<F extends (...args: any[]) => any>(fn: F): FRuns fn the first time only; later calls return the same result.
Parameters
| Name | Type |
|---|---|
fn | F |
Returns F
Example
const connect = func.once(() => createPool(config));nc.func.memoize()
memoize<F extends (...args: any[]) => any>(fn: F, options?: MemoizeOptions | ((...args: Parameters<F>) => unknown) | undefined): MemoizedFunction<F>Caches results by arguments. Supports expiry (ttl), a size limit (max) and async functions: a failed promise isn't cached, so the next call tries again.
Parameters
| Name | Type | Description |
|---|---|---|
fn | F | |
optionsoptional | MemoizeOptions | ((...args: Parameters<F>) => unknown) | Options, or a function that builds the cache key. |
Returns MemoizedFunction<F>
Example
const getUser = func.memoize((id) => db.users.find(id), { ttl: 60_000, max: 1000 });
getUser.delete(42); // forget one entry
getUser.clear(); // forget everythingnc.func.retry()
retry<T>(fn: (attempt: number) => T | PromiseLike<T>, options?: RetryOptions): Promise<Awaited<T>>Calls fn until it succeeds, waiting between attempts. If every attempt fails, the last error is thrown.
Parameters
| Name | Type | Description |
|---|---|---|
fn | (attempt: number) => T | PromiseLike<T> | Receives the attempt number, starting at 1. |
optionsoptional | RetryOptions |
Returns Promise<Awaited<T>>
Throws The last error, or an AbortError if the signal fires.
Example
const data = await func.retry(() => fetchJSON(url), {
attempts: 5,
delay: 200,
backoff: 2, // 200, 400, 800, 1600 ms
shouldRetry: (error) => !(error instanceof nc.errors.HttpError && error.status < 500),
});nc.func.timeout()
timeout<T>(promise: PromiseLike<T> | (() => T | PromiseLike<T>), ms: number, message?: string): Promise<Awaited<T>>Rejects with a TimeoutError if the promise takes longer than ms.
Parameters
| Name | Type | Description |
|---|---|---|
promise | PromiseLike<T> | (() => PromiseLike<T> | T) | A promise, or a function that returns one. |
ms | number | |
messageoptional | string |
Returns Promise<Awaited<T>>
Throws TimeoutError
Example
const res = await func.timeout(fetch(url), 5000);
const rows = await func.timeout(() => slowQuery(), 2000, "Database too slow");nc.func.delay()
delay<F extends (...args: any[]) => any>(fn: F, ms: number, ...args: Parameters<F>): Promise<Awaited<ReturnType<F>>>Waits ms, then calls fn with the arguments and resolves with its result.
Parameters
| Name | Type |
|---|---|
fn | F |
ms | number |
...args | Parameters<F> |
Returns Promise<Awaited<ReturnType<F>>>
Example
await func.delay(() => console.log("1s later"), 1000);nc.func.to()
to<T, E = Error>(promise: PromiseLike<T> | (() => T | PromiseLike<T>)): Promise<[E, undefined] | [null, Awaited<T>]>Awaits a promise and gives you [error, value] instead of throwing, so you don't need a try/catch.
Parameters
| Name | Type | Description |
|---|---|---|
promise | PromiseLike<T> | (() => PromiseLike<T> | T) | A promise, or a function (sync errors are caught too). |
Returns Promise<[E, undefined] | [null, Awaited<T>]>
Example
const [error, user] = await func.to(db.users.find(id));
if (error) return res.status(500).send(error.message);nc.func.attempt()
attempt<F extends (...args: any[]) => any>(fn: F): (...args: Parameters<F>) => Promise<[unknown, undefined] | [null, Awaited<ReturnType<F>>]>Wraps a function so it resolves to [error, value] instead of throwing.
Parameters
| Name | Type |
|---|---|
fn | F |
Returns (...args: Parameters<F>) => Promise<[unknown, undefined] | [null, Awaited<ReturnType<F>>]>
Example
const safeParse = func.attempt(JSON.parse);
const [error, data] = await safeParse(input);nc.func.promisify()
promisify(fn: (...args: any[]) => void): (...args: any[]) => Promise<any>Turns a callback-style function into one that returns a promise.
Parameters
| Name | Type |
|---|---|
fn | (...args: any[]) => void |
Returns (...args: any[]) => Promise<any>
Example
const lookup = func.promisify(dns.lookup);
const address = await lookup("nodejs.org");nc.func.pipe()7 overloads
pipe<A extends any[], B>(f1: (...args: A) => B): (...args: A) => B
pipe<A extends any[], B, C>(f1: (...args: A) => B, f2: (b: B) => C): (...args: A) => C
pipe<A extends any[], B, C, D>(f1: (...args: A) => B, f2: (b: B) => C, f3: (c: C) => D): (...args: A) => D
pipe<A extends any[], B, C, D, E>(f1: (...args: A) => B, f2: (b: B) => C, f3: (c: C) => D, f4: (d: D) => E): (...args: A) => E
pipe<A extends any[], B, C, D, E, F>(f1: (...args: A) => B, f2: (b: B) => C, f3: (c: C) => D, f4: (d: D) => E, f5: (e: E) => F): (...args: A) => F
pipe<A extends any[], B, C, D, E, F, G>(f1: (...args: A) => B, f2: (b: B) => C, f3: (c: C) => D, f4: (d: D) => E, f5: (e: E) => F, f6: (f: F) => G): (...args: A) => G
pipe(...fns: ((arg: any) => any)[]): (...args: any[]) => anyChains functions from left to right: pipe(f, g)(x) is g(f(x)). Types flow through up to 6 functions.
Parameters
| Name | Type |
|---|---|
f1 | (...args: A) => B |
f2 | (b: B) => C |
f3 | (c: C) => D |
f4 | (d: D) => E |
f5 | (e: E) => F |
f6 | (f: F) => G |
Returns (...args: A) => G
Example
const toSlug = func.pipe(nc.str.deburr, nc.str.kebabCase);
toSlug("Crème Brûlée"); // "creme-brulee"nc.func.compose()
compose(...fns: ((...args: any[]) => any)[]): (...args: any[]) => anyChains functions from right to left: compose(f, g)(x) is f(g(x)). pipe() reads in execution order and is better typed.
Parameters
| Name | Type |
|---|---|
...fns | ...((...args: any[]) => any) |
Returns (...args: any[]) => any
Example
const shout = func.compose((s) => s + "!", (s) => s.toUpperCase());
shout("hi"); // "HI!"nc.func.curry()
curry(fn: (...args: any[]) => any, arity?: number): (...args: any[]) => anyLets you pass a function's arguments a few at a time.
Parameters
| Name | Type | Description |
|---|---|---|
fn | (...args: any[]) => any | |
arityoptional | number | How many arguments to collect before calling.Default: fn.length |
Returns (...args: any[]) => any
Example
const add = func.curry((a, b, c) => a + b + c);
add(1)(2)(3); // 6
add(1, 2)(3); // 6nc.func.partial()
partial<P extends any[], R extends any[], T>(fn: (...args: [...P, ...R]) => T, ...preset: P): (...args: R) => TFixes the first arguments of a function.
Parameters
| Name | Type |
|---|---|
fn | (...args: [...P, ...R]) => T |
...preset | P |
Returns (...args: R) => T
Example
const hello = func.partial((greeting, name) => `${greeting}, ${name}!`, "Hello");
hello("Ada"); // "Hello, Ada!"nc.func.negate()
negate<A extends any[]>(predicate: (...args: A) => unknown): (...args: A) => booleanThe opposite of a predicate.
Parameters
| Name | Type |
|---|---|
predicate | (...args: A) => unknown |
Returns (...args: A) => boolean
Example
files.filter(func.negate((name) => name.startsWith(".")));nc.func.after()
after<F extends (...args: any[]) => any>(n: number, fn: F): (...args: Parameters<F>) => ReturnType<F> | undefinedDoes nothing for the first n - 1 calls, then calls fn every time.
Parameters
| Name | Type |
|---|---|
n | number |
fn | F |
Returns (...args: Parameters<F>) => ReturnType<F> | undefined
Example
const done = func.after(3, () => console.log("All 3 uploads finished"));
uploads.forEach((upload) => upload.on("end", done));nc.func.before()
before<F extends (...args: any[]) => any>(n: number, fn: F): (...args: Parameters<F>) => ReturnType<F> | undefinedCalls fn for the first n - 1 calls, then keeps returning the last result.
Parameters
| Name | Type |
|---|---|
n | number |
fn | F |
Returns (...args: Parameters<F>) => ReturnType<F> | undefined
Example
const tryLogin = func.before(4, login); // 3 real attempts at mostnc.func.noop()
noop(..._args: unknown[]): voidDoes nothing. Useful as a default callback.
nc.func.identity()
identity<T>(value: T): TReturns its argument.
Parameters
| Name | Type |
|---|---|
value | T |
Returns T
Example
[0, 1, 2].filter(func.identity); // [1, 2]Types
Import any of them in TypeScript with import type { DebouncedFunction } from "@ix-xs/node-comfort", or in JavaScript with import("@ix-xs/node-comfort").DebouncedFunction.
DebouncedFunction
A debounced function. flush() runs the pending call now, cancel() drops it, pending() tells you if there's one.
type DebouncedFunction = ((...args: Parameters<F>) => void) & { cancel(): void; flush(): void; pending(): boolean; }DebounceOptions
Options for debounce().
| Property | Type | Description |
|---|---|---|
leadingoptional | boolean | Call right away at the start of a burst. |
trailingoptional | boolean | Call once the burst is over. Defaults to true. |
maxWaitoptional | number | Never postpone a call longer than this, in ms, even if calls keep coming. |
MemoizedFunction
A memoized function, with its cache, clear() and delete(...args).
type MemoizedFunction = F & { cache: Map<unknown, { value: ReturnType<F>; expires: number; }>; clear(): void; delete(...args: Parameters<F>): boolean; }MemoizeOptions
Options for memoize().
| Property | Type | Description |
|---|---|---|
resolveroptional | ((...args: any[]) => unknown) | Builds the cache key from the arguments. Defaults to the arguments as JSON. |
ttloptional | number | How long a result stays cached, in ms. |
maxoptional | number | Maximum number of results; the least recently used goes first. |
cacheRejectionsoptional | boolean | Keep rejected promises. By default they're dropped so the next call tries again. |
RetryOptions
Options for retry().
| Property | Type | Description |
|---|---|---|
attemptsoptional | number | Total attempts, the first one included. Defaults to 3. |
delayoptional | number | ((attempt: number, error: unknown) => number) | Wait before the next attempt, in ms, or a function that computes it. |
backoffoptional | number | Multiplies the delay after each failure: 2 gives 200, 400, 800... Defaults to 1. |
maxDelayoptional | number | Cap for the computed delay, in ms. |
jitteroptional | number | Randomizes the delay, from 0 (none) to 1 (anywhere between 0 and 2x). Stops many clients from retrying in sync. |
shouldRetryoptional | ((error: unknown, attempt: number) => boolean | Promise<boolean>) | Return false to give up early, for example on a 4xx error. |
onRetryoptional | ((error: unknown, attempt: number, delay: number) => void) | Called before each wait, for logs or metrics. |
signaloptional | AbortSignal | Stops retrying. The attempt in progress isn't interrupted. |
ThrottledFunction
A throttled function, with cancel().
type ThrottledFunction = ((...args: Parameters<F>) => void) & { cancel(): void; }ThrottleOptions
Options for throttle().
| Property | Type | Description |
|---|---|---|
leadingoptional | boolean | Call right away on the first call. Defaults to true. |
trailingoptional | boolean | Make a last call with the latest arguments at the end of the interval. Defaults to true. |