Reference
yieldless/schedule
Reusable delay and stop policies for tuple retry and polling loops.
yieldless/schedule gives timing policy a name without introducing a runtime. A schedule is just a function from the latest attempt state to a decision: continue or stop, and how long to wait before trying again.
Use it when retry or polling policy is shared across call sites, when tests need to inspect timing decisions, or when a loop needs both a delay policy and a stopping rule.
Exports
type SchedulePolicy<E = unknown> = (state: ScheduleState<E>) => ScheduleDecisiontype ScheduleState<E = unknown> = { attempt, elapsedMs, error, signal }type ScheduleDecision = { continue: boolean, delayMs: number }fixedDelay(delayMs): SchedulePolicyexponentialBackoff({ baseDelayMs, factor, maxDelayMs, jitter }): SchedulePolicymaxAttempts(attempts): SchedulePolicymaxElapsedTime(maxElapsedMs): SchedulePolicycomposeSchedules(...policies): SchedulePolicygetScheduleDecision(policy, state): ScheduleDecisionwaitForSchedule(policy, state): Promise<SafeResult<void, E>>runScheduled(operation, policy, options): Promise<SafeResult<T, E>>continueNow(): SchedulePolicystopSchedule(): SchedulePolicy
Example
import {
composeSchedules,
exponentialBackoff,
maxAttempts,
runScheduled,
} from "yieldless/schedule";
const schedule = composeSchedules(
exponentialBackoff({
baseDelayMs: 100,
jitter: "full",
maxDelayMs: 2_000,
}),
maxAttempts(5),
);
const [error, user] = await runScheduled(
(attempt, signal) => loadUserFromApi(userId, { attempt, signal }),
schedule,
{ signal },
);Behavior notes
composeSchedules()stops when any child policy stops.- When multiple policies continue, the largest
delayMswins. maxAttempts(3)allows attempts1,2, and3, then stops before attempt4.waitForSchedule()returns the latesterrorwhen the policy stops.runScheduled()normalizes thrown operation failures into tuple errors.- All waiting is abort-aware through
yieldless/timer.
Good
Define retry policy once and reuse it.
const apiSchedule = composeSchedules(
exponentialBackoff({ baseDelayMs: 150, maxDelayMs: 5_000 }),
maxAttempts(4),
);Inspect a policy without sleeping.
const decision = getScheduleDecision(apiSchedule, {
attempt: 2,
elapsedMs: 500,
signal,
});Avoid
Do not hide business decisions inside a global schedule.
const schedule = composeSchedules(exponentialBackoff(), maxAttempts(10));Prefer naming the boundary-specific reason.
const githubApiSchedule = composeSchedules(
exponentialBackoff({ baseDelayMs: 250 }),
maxAttempts(3),
);