promise-retry
Wraps any promise-returning function with configurable retry and exponential backoff logic.
Repository Health
Technical Analysis
promise-retry is a minimal Node.js utility that wraps a promise-returning function with retry semantics, built directly on top of the battle-tested retry package’s exponential backoff engine. Rather than reimplementing backoff math, it exposes a single promiseRetry(fn, options) call that hands your function a retry callback: call it with an error to trigger another attempt, or resolve/reject normally to stop.
The library supports both (fn, options) and (options, fn) call signatures, forwards the current attempt number to your callback for logging or backoff-aware behavior, and lets you retry conditionally — for example, only on network timeouts — by inspecting the error before calling retry(err). It has no dependencies beyond retry and err-code, ships as a single ~50-line CommonJS module, and has remained API-stable since its 2.0 release.
What You Get
- A single
promiseRetry(fn, options)function with no other API surface to learn - Full access to the
retrymodule’s backoff options: retries, factor, minTimeout, maxTimeout, randomize - An attempt-number argument passed to your callback for logging or conditional logic
- Support for both
(fn, options)and(options, fn)call signatures
Common Use Cases
- Retrying flaky network requests with exponential backoff instead of a fixed delay loop
- Wrapping database connection attempts that may fail transiently on cold start
- Conditionally retrying only specific error codes (e.g. ETIMEDOUT) while failing fast on others
- Adding backoff-based retry to any existing promise-returning function without restructuring it
Under The Hood
Architecture
The entire library is a single exported function in index.js (~50 lines) with no internal layering. promiseRetry(fn, options) first normalizes the two supported call signatures, then creates a retry.operation(options) and wraps operation.attempt() in a new Promise executor. The wrapped fn is invoked with a retry callback and the current attempt number; if that callback is called, it throws a private, errcode-tagged EPROMISERETRY error carrying the original error as retried, which the outer .then(resolve, ...) handler detects via isRetryError() and routes into operation.retry(err) to schedule another attempt, or rejects with the original error once retries are exhausted. The module owns none of the actual backoff/attempt-counting logic — it is a thin adapter over the retry package’s operation object, so removing that dependency would eliminate all retry behavior.
Tech Stack
Plain CommonJS JavaScript targeting Node >=10, with exactly two runtime dependencies: retry (^0.12.0) for exponential backoff scheduling and err-code (^2.0.2) to attach a code property to the internal marker error. Test tooling is mocha (^8.0.1) with expect.js (^0.3.1) assertions and sleep-promise (^8.0.1) for delay-based fixtures, run via Travis CI (.travis.yml). There is no build step, bundler, or TypeScript — index.js ships as-is as the package’s main entry.
Code Quality
A single test/test.js file exercises the core behaviors with mocha/expect.js: retry-triggered re-invocation, attempt-number correctness, non-retry fulfillment and rejection paths, and the zero-retries edge case. Error handling is explicit throughout — errors are tagged and re-thrown rather than swallowed. There are no type annotations or TypeScript definitions bundled, and linting is limited to a .jshintrc (JSHint) rather than a modern linter; an .editorconfig enforces basic formatting consistency. Test coverage is adequate for the module’s small surface area but not comprehensive against edge cases like concurrent calls.
What Makes It Unique
promise-retry does not invent new backoff logic — that is delegated entirely to the well-established retry package. Its value is a small, deliberate API choice: an explicit retry(err) callback that callers invoke only when they decide a failure is retryable, rather than a wrapper that retries every rejection automatically. Combined with the alternate (options, fn) call signature for readability, this gives callers fine-grained conditional retry control without reimplementing attempt-counting or delay math themselves.