Skip to main content

Overview

Throw DelayedError from a job processor to move the job back to the delayed state, scheduling it for future processing.

Class

class DelayedError extends Error {
  constructor(message?: string);
}

Usage

import { Worker, DelayedError } from 'bullmq';

const worker = new Worker('myQueue', async (job) => {
  // Check if conditions are met to process
  const ready = await checkIfReady(job.data);
  
  if (!ready) {
    // Move back to delayed state
    throw new DelayedError('Conditions not met, delaying job');
  }
  
  // Process the job
  return await processJob(job.data);
});

Examples

Waiting for Dependencies

const processor = async (job) => {
  // Check if required data is available
  const data = await getData(job.data.dataId);
  
  if (!data) {
    // Data not ready yet, delay and try again later
    throw new DelayedError(
      `Data ${job.data.dataId} not available yet`
    );
  }
  
  return await processData(data);
};

Time-Based Conditions

const processor = async (job) => {
  const { scheduledTime } = job.data;
  
  if (Date.now() < scheduledTime) {
    // Too early to process
    throw new DelayedError(
      `Job scheduled for ${new Date(scheduledTime).toISOString()}`
    );
  }
  
  return await executeScheduledTask(job.data);
};

Resource Availability

const processor = async (job) => {
  const resource = await getResource();
  
  if (resource.isBusy) {
    // Resource busy, delay until later
    throw new DelayedError('Resource is busy');
  }
  
  try {
    resource.lock();
    return await useResource(resource, job.data);
  } finally {
    resource.unlock();
  }
};

External Service Status

const processor = async (job) => {
  const serviceStatus = await checkServiceStatus();
  
  if (serviceStatus === 'maintenance') {
    throw new DelayedError(
      'External service is in maintenance mode'
    );
  }
  
  return await callExternalService(job.data);
};

Behavior

When DelayedError is thrown:
  1. The job is moved from active to delayed state
  2. The job will be picked up again after the delay period
  3. The job’s attemptsMade counter is not incremented
  4. The job will be retried indefinitely until it succeeds or throws a different error

Setting Delay Duration

The delay duration is determined by the job’s existing delay settings. To control the delay:
// Set initial delay when adding the job
await queue.add('task', { data }, {
  delay: 5000,  // 5 seconds delay for DelayedError retries
});
Or change the delay dynamically:
const processor = async (job) => {
  const ready = await checkIfReady();
  
  if (!ready) {
    // Change delay before throwing
    await job.changeDelay(10000);  // 10 seconds
    throw new DelayedError('Not ready');
  }
  
  return await process(job.data);
};

Comparison with Other Approaches

DelayedError vs Regular Error

// With DelayedError (doesn't count as attempt)
const processor1 = async (job) => {
  if (!ready) {
    throw new DelayedError('Not ready');
    // Job goes to delayed, attemptsMade unchanged
  }
};

// With regular error (counts as attempt)
const processor2 = async (job) => {
  if (!ready) {
    throw new Error('Not ready');
    // Job may retry or fail based on attempts setting
  }
};

DelayedError vs Manual Delay

// With DelayedError
const processor1 = async (job) => {
  if (!ready) {
    throw new DelayedError('Waiting for resource');
  }
  return await process(job.data);
};

// Manual approach (less efficient)
const processor2 = async (job) => {
  while (!await checkIfReady()) {
    await new Promise(resolve => setTimeout(resolve, 5000));
  }
  return await process(job.data);
};

Infinite Delay Protection

Be careful with DelayedError to avoid infinite loops:
const processor = async (job) => {
  // Track how many times we've delayed
  const delayCount = job.data.delayCount || 0;
  
  if (delayCount > 10) {
    throw new Error('Exceeded maximum delay count');
  }
  
  const ready = await checkIfReady();
  
  if (!ready) {
    // Increment delay counter
    await job.updateData({
      ...job.data,
      delayCount: delayCount + 1,
    });
    
    throw new DelayedError(`Not ready (attempt ${delayCount + 1})`);
  }
  
  return await process(job.data);
};

Monitoring Delayed Jobs

import { Worker, DelayedError } from 'bullmq';

const worker = new Worker('myQueue', processor);

worker.on('failed', (job, error) => {
  if (error.name === 'DelayedError') {
    console.log(`Job ${job.id} delayed:`, error.message);
    // This is expected behavior, not a real failure
  } else {
    console.error(`Job ${job.id} failed:`, error.message);
  }
});

When to Use DelayedError

Use DelayedError when:
  • Waiting for external dependencies to become available
  • Waiting for specific time conditions
  • Resources are temporarily unavailable
  • External services are temporarily down
  • Data is being processed by another job
Don’t use DelayedError for:
  • Validation errors - use UnrecoverableError
  • Rate limiting - use RateLimitError
  • Permanent failures - throw regular Error or UnrecoverableError
  • Normal retry logic - use job attempts and backoff

Build docs developers (and LLMs) love