node-comfortv2.0.0

nc.async

Promises and concurrency: map with a limit, queues with priorities, mutexes, polling, and friendlier Promise.allSettled.

const { async } = require("@ix-xs/node-comfort");
import { sleep } from "@ix-xs/node-comfort/async";
const pages = await async.map(urls, (url) => fetchPage(url), { concurrency: 5 });

const limit = async.limit(2);
await Promise.all(files.map((file) => limit(() => upload(file))));

await async.poll(() => isServerReady(), { interval: 250, timeout: "10s" });
GuideExplanations and examples for nc.async.
Read the guide →

Functions

nc.async.sleep()

sleep(duration: string | number, options?: { signal?: AbortSignal; } | undefined): Promise<void>

Waits for a while. Same as nc.wait().

Parameters

NameType
durationnumber | string
optionsoptional{ signal?: AbortSignal }

Returns Promise<void>

Example

await async.sleep("250ms");

nc.async.map()

map<T, R>(items: Iterable<T> | AsyncIterable<T>, fn: (item: T, index: number) => R | PromiseLike<R>, options?: MapOptions): Promise<Awaited<R>[]>

Maps items through an async function, with at most concurrency calls at once. Results stay in order.

Parameters

NameType
itemsIterable<T> | AsyncIterable<T>
fn(item: T, index: number) => R | PromiseLike<R>
optionsoptionalMapOptions

Returns Promise<Array<Awaited<R>>>

Throws The first error, an AggregateError with stopOnError: false, or an AbortError.

Example

const users = await async.map(ids, (id) => api.getUser(id), { concurrency: 10 });

nc.async.forEach()

forEach<T>(items: Iterable<T> | AsyncIterable<T>, fn: (item: T, index: number) => unknown, options?: MapOptions): Promise<void>

Runs an async function for every item, with a concurrency limit.

Parameters

NameType
itemsIterable<T> | AsyncIterable<T>
fn(item: T, index: number) => unknown
optionsoptionalMapOptions

Returns Promise<void>

Example

await async.forEach(images, (image) => optimize(image), { concurrency: 4 });

nc.async.filter()

filter<T>(items: Iterable<T> | AsyncIterable<T>, predicate: (item: T, index: number) => unknown, options?: MapOptions): Promise<T[]>

Keeps the items that pass an async test, with a concurrency limit. Order is preserved.

Parameters

NameType
itemsIterable<T> | AsyncIterable<T>
predicate(item: T, index: number) => unknown
optionsoptionalMapOptions

Returns Promise<T[]>

Example

const alive = await async.filter(hosts, (host) => ping(host), { concurrency: 20 });

nc.async.series()

series<T>(tasks: Iterable<() => T | PromiseLike<T>>): Promise<Awaited<T>[]>

Runs async functions one after the other and returns their results.

Parameters

NameType
tasksIterable<() => T | PromiseLike<T>>

Returns Promise<Array<Awaited<T>>>

Example

await async.series([() => migrateUsers(), () => migrateOrders()]);

nc.async.limit()

limit(concurrency: number): Limiter

A limiter: wrap calls with it, and at most concurrency run at once while the rest wait their turn. Like p-limit.

Parameters

NameType
concurrencynumber

Returns Limiter

Example

const limit = async.limit(3);
const results = await Promise.all(urls.map((url) => limit(() => fetch(url))));

nc.async.queue()

queue(options?: QueueOptions): Queue

A task queue with a concurrency limit, priorities, pause and per-task timeouts.

Parameters

NameType
optionsoptionalQueueOptions

Returns Queue

Example

const jobs = async.queue({ concurrency: 2, timeout: "30s" });
jobs.add(() => sendEmail(a));
jobs.add(() => sendEmail(vip), { priority: 10 }); // jumps the line
await jobs.onIdle();

nc.async.mutex()

mutex(): Mutex

A mutex: tasks given to run() never overlap. Put it around read-modify-write steps, like updating a file or refreshing a token.

Returns Mutex

Example

const lock = async.mutex();
await lock.run(async () => {
  const data = nc.readJSON("./counter.json", { n: 0 });
  nc.writeJSON("./counter.json", { n: data.n + 1 });
});

nc.async.deferred()

deferred<T = void>(): Deferred<T>

A promise along with its resolve and reject. Handy to connect callbacks or events to await.

Returns Deferred<T>

Example

const ready = async.deferred();
socket.once("open", () => ready.resolve());
await ready.promise;

nc.async.settle()

settle<T>(promises: Iterable<T | PromiseLike<T>>): Promise<Settled<Awaited<T>>>

Waits for every promise and sorts successes from failures.

Parameters

NameType
promisesIterable<T | PromiseLike<T>>

Returns Promise<Settled<Awaited<T>>>

Example

const { fulfilled, rejected } = await async.settle(emails.map(send));
nc.info(`${fulfilled.length} sent, ${rejected.length} failed`);

nc.async.props()

props<T extends Record<string, unknown>>(object: T): Promise<{ [K in keyof T]: Awaited<T[K]>; }>

Like Promise.all, for an object of promises.

Parameters

NameType
objectT

Returns Promise<{ [K in keyof T]: Awaited<T[K]> }>

Example

const { user, orders } = await async.props({ user: getUser(id), orders: getOrders(id) });

nc.async.poll()

poll<T>(fn: () => T | PromiseLike<T>, options?: PollOptions): Promise<NonNullable<Awaited<T>>>

Calls fn until it returns something truthy, and resolves with it. Errors count as "not yet".

Parameters

NameType
fn() => T | PromiseLike<T>
optionsoptionalPollOptions

Returns Promise<NonNullable<Awaited<T>>>

Throws TimeoutError; AbortError

Example

await async.poll(() => fetch(healthUrl).then((res) => res.ok), { interval: "500ms", timeout: "30s" });

nc.async.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

NameTypeDescription
fn(attempt: number) => T | PromiseLike<T>Receives the attempt number, starting at 1.
optionsoptionalRetryOptions

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.async.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

NameTypeDescription
promisePromiseLike<T> | (() => PromiseLike<T> | T)A promise, or a function that returns one.
msnumber
messageoptionalstring

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");

Types

Import any of them in TypeScript with import type { Deferred } from "@ix-xs/node-comfort", or in JavaScript with import("@ix-xs/node-comfort").Deferred.

Deferred

A promise with its resolve and reject.

PropertyTypeDescription
promisePromise<T>
resolve(value: T | PromiseLike<T>) => void
reject(reason?: unknown) => void
settledbooleanWhether it's already resolved or rejected.

Limiter

A limiter from limit(). Call it with a function to run it when there's room.

type Limiter = (<T>(fn: () => T | PromiseLike<T>) => Promise<Awaited<T>>) & { readonly activeCount: number; readonly pendingCount: number; concurrency: number; clearQueue(): void; onIdle(): Promise<void>; }

MapOptions

Options for map(), forEach() and filter().

PropertyTypeDescription
concurrencyoptionalnumberHow many run at the same time. No limit by default.
stopOnErroroptionalbooleanStop at the first error. With false, everything runs and you get an AggregateError of all failures. Defaults to true.
signaloptionalAbortSignalStops starting new work and rejects with an AbortError.

Mutex

A mutex from mutex().

PropertyTypeDescription
run<T>(fn: () => T | PromiseLike<T>) => Promise<Awaited<T>>Runs fn once no other run is in progress.
lock() => Promise<() => void>Waits for the lock and gives you the function that releases it.
isLockedboolean

PollOptions

Options for poll().

PropertyTypeDescription
intervaloptionalstring | numberTime between tries. Defaults to 100 ms.
timeoutoptionalstring | numberGive up with a TimeoutError after this long.
signaloptionalAbortSignalStops with an AbortError.

Queue

A queue from queue().

PropertyTypeDescription
add<T>(task: () => T | PromiseLike<T>, options?: QueueAddOptions) => Promise<Awaited<T>>Adds a task; resolves with its result.
addAll<T>(tasks: (() => T | PromiseLike<T>)[], options?: QueueAddOptions) => Promise<Awaited<T>[]>Adds several tasks; resolves with all results.
pause() => QueueStops starting tasks. Running ones finish.
start() => QueueStarts again.
clear() => voidDrops the tasks that haven't started. Their promises never settle.
onIdle() => Promise<void>Resolves when nothing is waiting or running.
onEmpty() => Promise<void>Resolves when nothing is waiting.
sizenumberTasks waiting.
pendingnumberTasks running.
isPausedboolean
concurrencynumberCan be changed on the fly.

QueueAddOptions

Options for queue.add().

PropertyTypeDescription
priorityoptionalnumberHigher goes first; equal priorities keep their order. Defaults to 0.

QueueOptions

Options for queue().

PropertyTypeDescription
concurrencyoptionalnumberHow many tasks run at the same time. Defaults to 1.
autoStartoptionalbooleanStart right away. Defaults to true.
timeoutoptionalstring | numberTime limit per task, like "30s". A slower task rejects with a TimeoutError.

Settled

What settle() returns.

PropertyTypeDescription
fulfilledT[]Successful values, in order.
rejectedunknown[]Errors, in order.
resultsPromiseSettledResult<T>[]Everything, like Promise.allSettled.
node-comfort v2.0.0View the source