Build a worker that retries safely
Capped backoff, due-time filtering, a retry budget that ends in a dead letter, idempotency, and one testable tick of a worker.
What you end up with
The worker loop behind every background queue, including the three mistakes that turn one failing dependency into an outage.
- 5 steps
- 20 tests
- ≈25 minutes
- javascript
- 1
Work out how long to wait
backoffMs()Complete
backoffMs(attempts, baseMs, capMs). The first attempt waitsbaseMs, and each attempt after doubles it, never exceedingcapMs. Treatattemptsbelow 1 as 1. - 2
Pick out the jobs that are actually due
dueJobs()Complete
dueJobs(jobs, now). Each job is{ id, state, readyAt, attempts }. Return the jobs whosestateis'ready'and whosereadyAtis at or beforenow, in their original order. - 3
Decide a job's fate after a failure
afterFailure()Complete
afterFailure(job, now, maxAttempts, baseMs, capMs). Incrementattempts. When it reachesmaxAttempts, return the job withstate: 'dead'andreadyAtunchanged. Otherwise return it'ready'withreadyAtset tonowplusbackoffMs(attempts, baseMs, capMs). - 4
Do the same work twice without doing it twice
runOnce()Complete
runOnce(seen, key, result).seenmaps key to a stored result. Whenkeyis already there, return{ seen, result: stored, ran: false }withseenunchanged. Otherwise recordresultunderkeyand return{ seen, result, ran: true }. - 5
Run one tick of the worker
tick()Complete
tick(jobs, now, capacity, outcomes, limits).outcomesmaps job id totruefor success orfalsefor failure;limitsis{ maxAttempts, baseMs, capMs }. Take at mostcapacitydue jobs in order. A success becomesstate: 'done'; a failure goes throughafterFailure. Return the full job list in its original order, with untouched jobs unchanged.