Skip to main content
There are situations when it is useful to retry a job right away when it is being processed.

Using moveToWait

This can be handled using the moveToWait method. However, it is important to note that when a job is being processed by a worker, the worker keeps a lock on this job with a certain token value. For the moveToWait method to work, we need to pass said token so that it can unlock without error. Finally, we need to exit from the processor by throwing a special error (WaitingError) that will signal to the worker that the job has been retried so that it does not try to complete (or fail the job) instead.
import { WaitingError, Worker } from 'bullmq';

const worker = new Worker(
  'queueName',
  async (job: Job, token?: string) => {
    try {
      await doSomething();
    } catch (error) {
      await job.moveToWait(token);
      throw new WaitingError();
    }
  },
  { connection },
);

When to Use Manual Retrying

Temporary Failures

Retry immediately for transient errors like network blips

Rate Limiting

Move job back to wait when hitting rate limits

Resource Contention

Retry when a required resource is temporarily unavailable

Quick Recovery

Retry without waiting for backoff delay

Comparison with Standard Retries

FeatureManual RetryStandard Retry
TimingImmediateUses backoff delay
Attempts counterNot incrementedIncremented
ControlFull control in processorConfigured via options
Use caseTransient errorsPersistent errors

Example: Retry on Rate Limit

import { WaitingError, Worker, RateLimitError } from 'bullmq';

const worker = new Worker(
  'api-calls',
  async (job: Job, token?: string) => {
    try {
      const response = await callExternalAPI(job.data);
      return response;
    } catch (error) {
      // Check if it's a rate limit error
      if (error.response?.status === 429) {
        // Get retry-after header (in seconds)
        const retryAfter = error.response.headers['retry-after'];
        
        // Apply queue-wide rate limit
        await queue.rateLimit(retryAfter * 1000);
        
        // Move job back to wait
        await job.moveToWait(token);
        throw new WaitingError();
      }
      
      // For other errors, let standard retry logic handle it
      throw error;
    }
  },
  { connection },
);
Using moveToWait does not increment attemptsMade, but it does increment attemptsStarted. Be aware of this when checking attempt counts.

Retrying Failed Jobs

Learn about standard retry strategies

Rate Limiting

Implement rate limiting for your queues

Move To Wait API

API reference for moveToWait method

Build docs developers (and LLMs) love