auto
auto runs named asynchronous tasks in dependency order. Use it when each task may depend on the results of other tasks and independent tasks should still run concurrently.
Best for dependency graphs Use
autofor workflows such as loading configuration, fetching user data, preparing dependent requests, and combining the final result after all prerequisites are available.
Import
Section titled “Import”Root package:
import { auto } from "@vgerbot/async";Module subpath:
import { auto } from "@vgerbot/async/control-flow";Leaf subpath:
import { auto } from "@vgerbot/async/control-flow/auto";Quick example
Section titled “Quick example”Each task is declared by name. A task can be either a function with no dependencies or a tuple of [dependencies, task].
import { auto } from "@vgerbot/async";
const handle = auto<{ config: { baseUrl: string }; user: { id: number; name: string }; posts: Array<{ id: number; title: string }>; summary: string;}>( { config: async () => ({ baseUrl: "https://api.example.com" }),
user: [ ["config"], async ({ config }, token) => { await token.sleep(100); const response = await token.wrap( fetch(`${config.baseUrl}/users/1`), ); return response.json(); }, ],
posts: [ ["config", "user"], async ({ config, user }, token) => { const response = await token.wrap( fetch(`${config.baseUrl}/users/${user.id}/posts`), ); return response.json(); }, ],
summary: [ ["user", "posts"], async ({ user, posts }) => { return `${user.name} has ${posts.length} posts`; }, ], }, { concurrency: 2 },);
const result = await handle;
console.log(result.summary);When to use auto
Section titled “When to use auto”- Dependent async work: Model workflows where each step has named prerequisites and consumes their results.
- Parallel where possible: Independent tasks are scheduled concurrently, while dependent tasks wait for their inputs.
- Typed result maps: Describe the final result shape once and get strongly typed dependency inputs.
- Cancelable workflows: Every task receives a
CancellableToken, and the returned handle can stop the graph.
function auto<TResults extends Record<string, unknown>>( tasks: AutoTasks<TResults>, options: AutoResolveOptions<TResults>,): CancellableHandle<AutoResolveResult<TResults>>;
function auto<TResults extends Record<string, unknown>>( tasks: AutoTasks<TResults>, options?: AutoRejectOptions<TResults>,): CancellableHandle<AutoResult<TResults>>;Parameters
Section titled “Parameters”A map where each key is a task name and each value describes how that task should run.
type AutoTasks<TResults extends Record<string, unknown>> = { [K in keyof TResults]: AutoTask<TResults, TResults[K]>;};A task can be written in two forms:
const taskWithoutDependencies = async (results, token) => value;
const taskWithDependencies = [ ["dependencyA", "dependencyB"], async (results, token) => value,];The results object only contains completed dependencies. When dependencies are declared with tuple syntax, TypeScript narrows results to the named dependencies.
options
Section titled “options”| Option | Type | Default | Description |
|---|---|---|---|
concurrency |
number |
Infinity |
Maximum number of active tasks. Values below 1 are normalized to 1. |
errorMode |
"reject" | "resolve" |
"reject" |
Controls whether task failures reject the handle or resolve with partial results and an error. |
name |
string |
"auto" |
Name used in cancellation and task error labels. |
signal |
AbortSignal |
undefined |
External signal linked to the workflow. |
timeout |
number |
undefined |
Cancels the workflow after the specified milliseconds. |
fallback |
value or function | undefined |
Fallback value used by the underlying cancellable task when it rejects. |
retry |
RetryOptions |
undefined |
Retry configuration applied to the whole auto workflow. |
onCancel |
function | undefined |
Called when the workflow is cancelled. |
onRetry |
function | undefined |
Called before a retry attempt. |
Return value
Section titled “Return value”auto returns a CancellableHandle.
const handle = auto(tasks);
await handle;handle.cancel("No longer needed");handle.isCancelled();handle.signal;In default reject mode, handle resolves to the complete result map.
const results = await auto(tasks);In resolve mode, handle always resolves to an object containing partial results and an optional AutoExecutionError.
const { results, error } = await auto(tasks, { errorMode: "resolve",});Dependency execution model
Section titled “Dependency execution model”auto repeatedly schedules ready tasks until the graph completes or fails.
- Parse task declarations.
- Validate that every dependency points to a known task.
- Start tasks whose dependencies are already completed.
- Respect
concurrencywhile scheduling more ready tasks. - Store each task result by its task name.
- Stop scheduling new tasks after the first non-cancellation error.
- Wait for already active tasks to settle before returning or throwing.
const handle = auto( { a: async () => "a", b: async () => "b", c: [["a", "b"], async ({ a, b }) => `${a}${b}`], }, { concurrency: 2 },);
console.log(await handle);// { a: "a", b: "b", c: "ab" }Error handling
Section titled “Error handling”Reject mode
Section titled “Reject mode”Reject mode is the default. If a task fails, handle rejects with AutoExecutionError.
import { AutoExecutionError, auto } from "@vgerbot/async";
const handle = auto<{ user: { id: number }; posts: unknown[];}>({ user: async () => ({ id: 1 }), posts: [ ["user"], async () => { throw new Error("Posts service unavailable"); }, ],});
try { await handle;} catch (error) { if (error instanceof AutoExecutionError) { console.log(error.taskName); console.log(error.cause); console.log(error.partialResults); }}Resolve mode
Section titled “Resolve mode”Resolve mode is useful when partial results are valuable and failure should be handled as data.
const { results, error } = await auto<{ profile: { id: number }; recommendations: string[];}>( { profile: async () => ({ id: 1 }), recommendations: [ ["profile"], async () => { throw new Error("Recommendation service failed"); }, ], }, { errorMode: "resolve" },);
if (error) { console.log(results.profile); console.warn(error.message);}Cancellation
Section titled “Cancellation”Every task receives a CancellableToken. Use it to make sleeps, fetches, nested handles, and explicit checks react to cancellation.
const handle = auto( { slowTask: async (_results, token) => { await token.sleep(5_000); token.throwIfCancelled(); return "done"; }, }, { name: "loadDashboard" },);
setTimeout(() => { handle.cancel("User navigated away");}, 100);
await handle;Cancellation rejects with
CancelErrorWhen cancellation occurs,autopropagates the cancellation error instead of wrapping it inAutoExecutionError.
Invalid dependency graphs
Section titled “Invalid dependency graphs”auto validates dependency names before running tasks.
await auto({ user: [["missingConfig"], async () => ({ id: 1 })],});
// Error: auto task "user" depends on unknown task "missingConfig"If tasks cannot be resolved because of a cycle, the promise rejects with a cycle error.
await auto({ a: [["b"], async () => 1], b: [["a"], async () => 2],});
// Error: auto cannot resolve dependencies (possible cycle): a[b]; b[a]TypeScript tips
Section titled “TypeScript tips”Use a result map type to make dependency inputs precise.
type WorkflowResults = { config: { apiUrl: string }; session: { token: string }; dashboard: { widgets: string[] };};
const handle = auto<WorkflowResults>({ config: async () => ({ apiUrl: "/api" }), session: [["config"], async ({ config }) => ({ token: config.apiUrl })], dashboard: [ ["config", "session"], async ({ config, session }) => ({ widgets: [`${config.apiUrl}:${session.token}`], }), ],});Use as const when TypeScript widens dependency arrays too much.
const handle = auto<WorkflowResults>({ config: [[], async () => ({ apiUrl: "/api" })] as const, session: [["config"], async ({ config }) => ({ token: config.apiUrl })] as const, dashboard: [ ["config", "session"], async ({ config, session }) => ({ widgets: [`${config.apiUrl}:${session.token}`], }), ] as const,});Related APIs
Section titled “Related APIs”parallelruns independent tasks concurrently.seriesruns tasks one after another.waterfallpasses each task result into the next task.queuemanages a long-lived worker queue.cancellablecreates a cancellable handle directly.