Yieldless
Reference

yieldless/task

Structured concurrency with a shared AbortSignal and sibling cancellation.

yieldless/task gives you a small structured-concurrency primitive for normal async functions.

Exports

  • type TaskFactory<T> = (signal: AbortSignal) => PromiseLike<T> | T
  • interface TaskGroup { readonly signal: AbortSignal; spawn(task): Promise<T> }
  • runTaskGroup(operation, options?): Promise<T>

What runTaskGroup() guarantees

  • All spawned tasks share one AbortSignal
  • The group can inherit cancellation from an upstream AbortSignal
  • The first task failure aborts the group immediately
  • The group waits for every child task to settle before returning
  • The original failure is rethrown after cleanup

Example

import { runTaskGroup } from "yieldless/task";

const controller = new AbortController();

const repository = await runTaskGroup(async (group) => {
  const refs = group.spawn((signal) => loadRefs(signal));
  const branches = group.spawn((signal) => loadBranches(signal));

  return {
    refs: await refs,
    branches: await branches,
  };
}, {
  signal: controller.signal,
});

What it does not guarantee

Task cancellation is cooperative. Your spawned function must check or forward the signal for cancellation to take effect.

group.spawn((signal) => runCommand("git", ["fetch"], { signal }));

Good fits

  • Parallel repository reads that should rise and fall together
  • Request-scoped fan-out in HTTP handlers
  • Background jobs that launch multiple abortable I/O operations

Good

Use the group for work with one lifecycle.

const summary = await runTaskGroup(async (group) => {
  const status = group.spawn((signal) => readStatus(repoPath, signal));
  const branches = group.spawn((signal) => readBranches(repoPath, signal));

  return {
    status: await status,
    branches: await branches,
  };
}, {
  signal: request.signal,
});

Let failures throw inside runTaskGroup() when the body is promise-native. If you need tuple-native fan-out, use yieldless/all.

Avoid

Do not spawn children after the group body has returned.

let groupRef: TaskGroup | undefined;

await runTaskGroup((group) => {
  groupRef = group;
  return "done";
});

groupRef?.spawn(loadLater);

Do not swallow child failures unless you intentionally convert them to successful values. The group uses thrown failures to abort siblings.

On this page