← All tasksNode.js · Senior
A job queue with retries and dead letters
Write createQueue({ handler, retries, backoffMs }). push(job) queues a job and returns a promise with the result of handler(job, attempt). Jobs run one at a time, in the order they were added.
If handler throws, the job is retried up to retries times with a pause of backoffMs, then backoffMs * 2, backoffMs * 4... (exponential backoff). When attempts run out, the promise rejects with the last error and the job goes to deadLetter(). One job's failure doesn't stop the queue.
Ctrl+Enter runs the tests. Tab indents; to leave the editor with the keyboard, press Esc and then Tab. Your code is saved in this browser.
Tests (6)
const q = createQueue({ handler: async (job) => job.n * 10, retries: 2, backoffMs: 5 }); expect(await q.push({ n: 4 })).toBe(40); expect(q.deadLetter()).toEqual([]);
let calls = 0; const q = createQueue({ handler: async () => { calls++; if (calls < 3) throw new Error('busy'); return 'done'; }, retries: 3, backoffMs: 5 }); expect(await q.push('job')).toBe('done'); expect(calls).toBe(3);
const handler = fn(async () => { throw new Error('bad payload'); }); const q = createQueue({ handler, retries: 2, backoffMs: 5 }); await expect(q.push({ id: 7 })).rejects.toThrow('bad payload'); expect(handler).toHaveBeenCalledTimes(3); expect(q.deadLetter()).toEqual([{ id: 7 }]);
const times = []; const start = Date.now(); const q = createQueue({ handler: async () => { times.push(Date.now() - start); throw new Error('x'); }, retries: 3, backoffMs: 20 }); await expect(q.push(1)).rejects.toThrow('x'); const gaps = times.slice(1).map((t, i) => t - times[i]); expect(gaps[0] >= 15 && gaps[1] >= 35 && gaps[2] >= 75).toBe(true);
const order = []; let active = 0; let maxActive = 0; const q = createQueue({ handler: async (job) => { active++; maxActive = Math.max(maxActive, active); await wait(10); order.push(job); active--; return job; }, retries: 0, backoffMs: 5 }); await Promise.all([q.push('a'), q.push('b'), q.push('c')]); expect(order).toEqual(['a', 'b', 'c']); expect(maxActive).toBe(1);
const q = createQueue({ handler: async (job) => { if (job === 'bad') throw new Error('nope'); return job; }, retries: 1, backoffMs: 5 }); const bad = q.push('bad').catch((e) => e.message); const good = q.push('good'); expect(await bad).toBe('nope'); expect(await good).toBe('good'); expect(q.deadLetter()).toEqual(['bad']);