Who this is for: projects on @julr/tenace that want hedging, adaptive concurrency, a shipped Redis breaker store, framework middleware, or CommonJS and Node 20 support.
The two libraries cover much of the same ground and use similar words: execute() and settle(), typed errors, fallbacks that remove errors from the type, waitFor, batches, a virtual clock. Most of the port is renaming. Two changes need care: the order is reversed, and typed errors are keyed by code, not _tag.
match<T, E, HextendsOutcomeHandlers<T, E, unknown>>(outcome:Outcome<T, E>, handlers:H):ReturnType<H[keyofH] & ((...args:any) =>any)>;
}
The result of an execution: either a value or an error. Strategies pass outcomes around instead of throwing.
E is the error type when it's known (see pipeline.settle()), unknown otherwise.
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
CircuitBreakerOptions<any>.consecutiveFailures?: number |undefined
Break after this many failures in a row (a success resets the count). Simple and good for low traffic.
When set without failureRatio, the ratio check is off and only this rule applies.
Like executeOutcome, but the error is typed: the errors this pipeline's strategies can raise (E), the errors your
callback returns with Outcome.fromError(…) (E2), or UnexpectedError wrapping anything else (a thrown bug, a
client error). Pair it with Outcome.match() for exhaustive handling:
constoutcome=await pipeline.settle(async () => (user ? user : Outcome.fromError(newNotFound(id))));
match<T, E, HextendsOutcomeHandlers<T, E, unknown>>(outcome:Outcome<T, E>, handlers:H):ReturnType<H[keyofH] & ((...args:any) =>any)>;
}
The result of an execution: either a value or an error. Strategies pass outcomes around instead of throwing.
E is the error type when it's known (see pipeline.settle()), unknown otherwise.
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
String(
error: UnexpectedError
error.
Error.cause?: unknown
cause)}`,
});
The chain is reversed, and the retry gets tenace’s backoff explicitly (exponential from 100 ms, full jitter); ours defaults to a constant 2 s. An isolated circuit has its own code, so match asks for a handler for it.
match<T, E, HextendsOutcomeHandlers<T, E, unknown>>(outcome:Outcome<T, E>, handlers:H):ReturnType<H[keyofH] & ((...args:any) =>any)>;
}
The result of an execution: either a value or an error. Strategies pass outcomes around instead of throwing.
E is the error type when it's known (see pipeline.settle()), unknown otherwise.
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Represents the completion of an asynchronous operation
Promise<
typeOutcome<T, E=unknown> = {
readonlyok:true;
readonlyresult:T;
} | {
readonlyok:false;
readonlyerror:E;
}
The result of an execution: either a value or an error. Strategies pass outcomes around instead of throwing.
E is the error type when it's known (see pipeline.settle()), unknown otherwise.
match<T, E, HextendsOutcomeHandlers<T, E, unknown>>(outcome:Outcome<T, E>, handlers:H):ReturnType<H[keyofH] & ((...args:any) =>any)>;
}
The result of an execution: either a value or an error. Strategies pass outcomes around instead of throwing.
E is the error type when it's known (see pipeline.settle()), unknown otherwise.
match<T, E, HextendsOutcomeHandlers<T, E, unknown>>(outcome:Outcome<T, E>, handlers:H):ReturnType<H[keyofH] & ((...args:any) =>any)>;
}
The result of an execution: either a value or an error. Strategies pass outcomes around instead of throwing.
E is the error type when it's known (see pipeline.settle()), unknown otherwise.
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Like executeOutcome, but the error is typed: the errors this pipeline's strategies can raise (E), the errors your
callback returns with Outcome.fromError(…) (E2), or UnexpectedError wrapping anything else (a thrown bug, a
client error). Pair it with Outcome.match() for exhaustive handling:
constoutcome=await pipeline.settle(async () => (user ? user : Outcome.fromError(newNotFound(id))));
settle() understands a returned Outcome, so NOT_FOUND joins the error type and addFallbackOn takes it out again. execute() does not unwrap a returned Outcome: use settle() (or Outcome.unwrap()) when your callback returns one.
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Like executeOutcome, but the error is typed: the errors this pipeline's strategies can raise (E), the errors your
callback returns with Outcome.fromError(…) (E2), or UnexpectedError wrapping anything else (a thrown bug, a
client error). Pair it with Outcome.match() for exhaustive handling:
constoutcome=await pipeline.settle(async () => (user ? user : Outcome.fromError(newNotFound(id))));
Returned errors count as failures for the strategies (they can be retried) unless your predicates say otherwise.
settle(() =>new
var Promise:PromiseConstructor
new <string>(executor: (resolve: (value:string|PromiseLike<string>) =>void, reject: (reason?:any) =>void) =>void) =>Promise<string>
Creates a new Promise.
@param ― executor A callback used to initialize the promise. This callback is passed two arguments:
a resolve callback used to resolve the promise with a value or the result of another promise,
and a reject callback used to reject the promise with a provided reason or error.
A per-execution timeProvider drives retry delays, timeouts and hedging delays. Stateful strategies (breaker, limiters, cache, lock) keep the pipeline’s clock; pass timeProvider to the builder to fake those too.
Retry defaults. tenace retries every error, exponential from 100 ms with full jitter. Ours (Polly’s): 3 retries, a constant 2 s, and only transient errors: 4xx client errors (except 408/429), caller aborts and rejections from other strategies are skipped. Pass shouldHandle: ({ outcome }) => !outcome.ok to retry everything.
Breaker defaults. tenace opens after 5 consecutive failures and stays open 30 s. Ours, without options: 10% failures over at least 100 calls in 30 s, open 5 s.
Breaker classification. tenace’s classify can return 'success', 'failure' or 'ignored'. Our shouldHandle only decides failure; a result it doesn’t handle is a success if the call succeeded and ignored if it failed.
Half-open successes. tenace’s successes is a count; our halfOpenSuccessThreshold is a fraction of the probes (0.8 with halfOpenMaxCalls: 5 needs 4).
Open circuit and retries. Both skip retrying an open circuit. tenace retries it when retryOn lists CircuitOpenError; ours never does, whatever shouldHandle says.
Timeout mode. tenace’s default (aggressive) and ours both reject at the deadline and abort the signal. tenace’s cooperative keeps a success that arrives late; ours keeps it only with keepLateResult: true.
Typed errors are opt-in. Every pipeline has settle(), but execute() remains the main path and throws. The error type comes from the builder; addFallback({ shouldHandle }) with a predicate removes nothing from it (the predicate isn’t analysed), so prefer addFallbackOn(codes, …).
Instrumentation. tenace’s events (retry.scheduled, circuit.state_changed…) map to our telemetry events (OnRetry, OnCircuitOpened…). See Events.
Bundle size. retry + breaker + timeout is 7.1 KB here against tenace’s 10.9 KB, but our full builder is 15.1 KB against tenace’s 11.7 KB. Use PipelineBuilder with xStrategy() factories in browser code (see Bundle size).