TanStack / TanStack/pacer

Async utilities count swallowed retryer failures as successes and call onSuccess(undefined)

Open
#247 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
769
Forks
64
Avg merge
1d 21h
Merged PRs (30d)
3

Description

Describe the bug

AsyncRetryer.execute resolves undefined in three non-success cases: the retryer is disabled (enabled: false), all attempts failed with retryer-level throwOnError: false, or the execution was aborted.

Every async utility that wraps an internal retryer (AsyncDebouncer, AsyncThrottler, AsyncRateLimiter, AsyncQueuer, AsyncBatcher) treats that resolved undefined as a success: successCount increments, lastResult is overwritten with undefined, and onSuccess fires with undefined as the result — e.g. onSuccess: (result: string) => ... receives undefined at runtime.

Repro sketch:

const debouncer = new AsyncDebouncer(
  async () => { throw new Error('always fails') },
  {
    wait: 100,
    asyncRetryerOptions: { maxAttempts: 2, throwOnError: false },
    onSuccess: (result) => console.log('success?', result), // fires with undefined
  },
)
Design questions to settle
  • Should a swallowed final failure settle as an error at the parent level (increment errorCount, fire onError) even though the retryer didn't throw?
  • Should an aborted execution count as neither success nor error (probably — it already returns early in some paths)?
  • AsyncRetryer.execute's undefined return is ambiguous by design; distinguishing outcomes may need an internal result envelope or reading the retryer's final state (lastError) after execute resolves.

Noted during review of #246 (see CodeRabbit feedback there) — split out because the fix changes observable semantics across all five utilities rather than being a bugfix in one.

🤖 Generated with Claude Code

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at AsyncRetryer.execute and trace how AsyncDebouncer, AsyncThrottler, AsyncRateLimiter, AsyncQueuer, and AsyncBatcher handle its resolved undefined value. Reproduce disabled, swallowed-failure, and aborted executions, then define and verify the intended success, error, callback, and count behavior across all five utilities.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.