Find the result by durable identity.
Start with one job ID. Locate its output object or database record, the pointer your app stores and the customer request that reads it. Confirm these belong to the same account and task.
If the object exists but the app references another key, the worker succeeded at the wrong boundary. If the pointer exists but the object does not, inspect write ordering, cleanup and lifecycle rules.
Await every required step.
A returned promise must encompass output generation and its durable publication. forEach with an async callback does not wait for all its callbacks before the enclosing function continues.
// Sequential publication, with errors propagated:
for (const item of items) {
await publishItem(item);
}
await verifyOutputExists(outputKey);
await markResultReady(jobId, outputKey);Use bounded concurrency where needed. Do not replace this with unlimited parallel uploads. Verification here means a meaningful storage or database check in your implementation.
Design for an interrupted worker.
Assume a worker can stop after writing part of the output. Use an attempt-specific temporary destination or another idempotent publication design. A retry should not produce duplicate customer-visible records or expose an incomplete artifact.
If you use BullMQ, distinguish a processor that resolves from a stalled job that lost its lock. These are different investigations. CPU-heavy code can also interfere with the worker’s ability to renew a lock.
Test the final retrieval.
Interrupt a development job between upload and publication, then retry it. Ask for the result through the same authorised route the customer uses. Check its content, not just existence or a completed job counter.
Reference: BullMQ’s stalled job documentation. See the local failure lab for a completed-without-artifact case.