/skills/fuz-stack/references/async-patterns
  • docs
  • skills
  • fuz-stack
    • Async Patterns
    • Code Generation
    • Common Utilities
    • CSS Patterns
    • Dependency Injection
    • Documentation System
    • File Organization
    • mdz — Strict Markdown Dialect
    • Approved npm Dependencies
    • Path References in Documentation
    • Approved Rust Dependencies
    • Rust Patterns for the Fuz Ecosystem
    • Rust Performance Patterns
    • Rust Spine & Consumer Servers
    • Svelte 5 Patterns
    • Task Patterns
    • Testing Patterns
    • TSDoc Comment Style Guide
    • Twin Implementations (TS ↔ Rust)
    • Type Utilities
    • WASM Patterns for the Fuz Ecosystem
    • Zod Schemas
  • grimoire
  • tools
  • hash

Async Patterns

Async concurrency utilities in @fuzdev/fuz_util/async.ts and @fuzdev/fuz_util/dag.ts — controlled concurrency for file I/O, network requests, task execution, and DAG scheduling.

AsyncStatus

Lifecycle type for tracking async operations in UI:

type AsyncStatus = 'initial' | 'pending' | 'success' | 'failure';

Basic Utilities

wait

await wait(500); // wait 500ms await wait(); // wait 0ms (next macrotask via setTimeout)

is_promise

Type guard for Promise/thenable detection:

if (is_promise(value)) { const result = await value; }

Deferred Pattern

Separates promise creation from resolution — external control over when and how a promise resolves. Create with create_deferred():

interface Deferred<T> { promise: Promise<T>; resolve: (value: T) => void; reject: (reason: any) => void; }const deferred = create_deferred<string>(); // Pass the promise to a consumer some_async_consumer(deferred.promise); // Resolve later from the producer deferred.resolve('done');

When to use Deferred

  • Coordinating between independent async flows (e.g., DAG node dependencies)
  • Bridging callback-based APIs with promise-based code
  • Signaling completion from one context to waiters in another

Used internally by run_dag() and throttle.

Concurrent Operations

Three functions for bounded concurrency over iterables. All require concurrency >= 1, accept an optional AbortSignal, and pass both item and index to fn (which may return synchronously).

Choosing the right function

FunctionReturns resultsFail behaviorUse when
each_concurrentNoFail-fastSide effects only
map_concurrentYes (ordered)Fail-fastTransform + collect
map_concurrent_settledYes (settled)Collects all (no throw)Best-effort collection

each_concurrent

Side effects only, no result collection:

const each_concurrent: <T>( items: Iterable<T>, concurrency: number, fn: (item: T, index: number) => Promise<void> | void, signal?: AbortSignal, ) => Promise<void>;await each_concurrent( file_paths, 5, // max 5 concurrent deletions async (path) => { await unlink(path); }, );

Fail-fast: On first rejection, stops spawning new workers and rejects; with signal, aborts immediately.

map_concurrent

Like each_concurrent, collecting results in input order:

const map_concurrent: <T, R>( items: Iterable<T>, concurrency: number, fn: (item: T, index: number) => Promise<R> | R, signal?: AbortSignal, ) => Promise<Array<R>>;const results = await map_concurrent( file_paths, 5, // max 5 concurrent reads async (path) => readFile(path, 'utf8'), ); // results[i] corresponds to file_paths[i]

Fail-fast: On first rejection, stops spawning and rejects. Partial results are lost.

map_concurrent_settled

Follows Promise.allSettled pattern — never rejects the outer promise:

const map_concurrent_settled: <T, R>( items: Iterable<T>, concurrency: number, fn: (item: T, index: number) => Promise<R> | R, signal?: AbortSignal, ) => Promise<Array<PromiseSettledResult<R>>>;const results = await map_concurrent_settled(urls, 5, fetch); for (const [i, result] of results.entries()) { if (result.status === 'fulfilled') { console.log(`${urls[i]}: ${result.value.status}`); } else { console.error(`${urls[i]}: ${result.reason}`); } }

Abort behavior: On abort, resolves with partial results — completed items keep their real settlements, in-flight items are rejected with abort reason. Items never pulled from the iterator are absent from the results array.

All three cap in-flight work at concurrency, spawning the next item as each settles. Empty iterables resolve immediately.

AsyncSemaphore

Class-based concurrency limiter — more flexible than concurrent map/each:

const semaphore = new AsyncSemaphore(3); // max 3 concurrent async function do_work(item: string): Promise<void> { await semaphore.acquire(); // blocks if 3 already active try { await process(item); } finally { semaphore.release(); // free the slot } }

Constructor requires permits >= 0.

Infinity permits

new AsyncSemaphore(Infinity) — acquire() always resolves immediately. Useful for disabling concurrency limits without changing call sites.

Used by run_dag() to bound node execution concurrency.

DAG Execution

run_dag() in @fuzdev/fuz_util/dag.ts executes dependency-graph nodes concurrently. Nodes declare dependencies via depends_on; independent nodes run in parallel up to max_concurrency. Uses AsyncSemaphore for concurrency and Deferred for dependency signaling.

import {run_dag, type DagNode} from '@fuzdev/fuz_util/dag.ts'; interface BuildStep extends DagNode { command: string; } const result = await run_dag<BuildStep>({ nodes, execute: async (node) => { await run_command(node.command); }, max_concurrency: 4, stop_on_failure: true, // default }); if (!result.success) { console.error(result.error); // e.g., "2 node(s) failed" }

DagNode interface

Sortable is from @fuzdev/fuz_util/sort.ts (topological sort validation).

interface DagNode extends Sortable { id: string; depends_on?: Array<string>; }

DagOptions

interface DagOptions<T extends DagNode> { nodes: Array<T>; execute: (node: T) => Promise<void>; on_error?: (node: T, error: Error) => Promise<void>; on_skip?: (node: T, reason: string) => Promise<void>; should_skip?: (node: T) => boolean; max_concurrency?: number; // default: Infinity stop_on_failure?: boolean; // default: true skip_validation?: boolean; // default: false }

DagResult

interface DagResult { success: boolean; results: Map<string, DagNodeResult>; completed: number; failed: number; skipped: number; duration_ms: number; error?: string; }

DagNodeResult

interface DagNodeResult { id: string; status: 'completed' | 'failed' | 'skipped'; error?: string; duration_ms: number; }

Failed dependency nodes cascade — dependents are skipped with reason 'dependency failed'.

Quick Reference

ExportModuleTypePurpose
AsyncStatusasync.tsTypeLifecycle status for async operations
waitasync.tsFunctionPromise-based delay
is_promiseasync.tsFunctionType guard for Promise/thenable
Deferred<T>async.tsInterfacePromise with external resolve/reject
create_deferredasync.tsFunctionCreates a Deferred
each_concurrentasync.tsFunctionConcurrent side effects, fail-fast
map_concurrentasync.tsFunctionConcurrent map with ordered results, fail-fast
map_concurrent_settledasync.tsFunctionConcurrent map, allSettled pattern
AsyncSemaphoreasync.tsClassConcurrency limiter with acquire/release
run_dagdag.tsFunctionConcurrent DAG executor
DagNodedag.tsInterfaceMinimum shape for a DAG node
DagOptionsdag.tsInterfaceOptions for run_dag
DagResultdag.tsInterfaceAggregated DAG execution result
DagNodeResultdag.tsInterfacePer-node execution result