← Усі задачіNode.js · Senior

Черга задач з ретраями й dead letter

Напишіть createQueue({ handler, retries, backoffMs }). push(job) ставить задачу в чергу й повертає проміс із результатом handler(job, attempt). Задачі виконуються по одній, у порядку додавання.

Якщо handler кидає помилку, задача повторюється до retries разів із паузою backoffMs, потім backoffMs * 2, backoffMs * 4... (експоненційна затримка). Коли спроби скінчилися, проміс відхиляється останньою помилкою, а задача потрапляє в deadLetter(). Помилка однієї задачі не зупиняє чергу.

Тести (6)

  1. 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([]);
  2. 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);
  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 }]);
  4. 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);
  5. 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);
  6. 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']);