← Alle AufgabenNode.js · Senior
Eine Job-Queue mit Wiederholungen und Dead Letters
Schreib createQueue({ handler, retries, backoffMs }). push(job) reiht einen Job ein und liefert ein Promise mit dem Ergebnis von handler(job, attempt). Jobs laufen einzeln, in der Reihenfolge, in der sie hinzugefügt wurden.
Wirft handler, wird der Job bis zu retries Mal wiederholt, mit einer Pause von backoffMs, dann backoffMs * 2, backoffMs * 4... (exponentielles Backoff). Sind die Versuche aufgebraucht, wird das Promise mit dem letzten Fehler abgelehnt, und der Job landet in deadLetter(). Der Fehlschlag eines Jobs hält die Queue nicht an.
Strg+Enter führt die Tests aus. Tab rückt ein; um den Editor per Tastatur zu verlassen, drücke Esc und dann Tab. Der Code wird in diesem Browser gespeichert.
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']);