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
Describe the bug
AsyncRetryer.executeresolvesundefinedin three non-success cases: the retryer is disabled (enabled: false), all attempts failed with retryer-levelthrowOnError: false, or the execution was aborted.Every async utility that wraps an internal retryer (AsyncDebouncer, AsyncThrottler, AsyncRateLimiter, AsyncQueuer, AsyncBatcher) treats that resolved
undefinedas a success:successCountincrements,lastResultis overwritten withundefined, andonSuccessfires withundefinedas the result — e.g.onSuccess: (result: string) => ...receivesundefinedat runtime.Repro sketch:
Design questions to settle
errorCount, fireonError) even though the retryer didn't throw?AsyncRetryer.execute'sundefinedreturn is ambiguous by design; distinguishing outcomes may need an internal result envelope or reading the retryer's final state (lastError) afterexecuteresolves.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