nest-queue-batch
v1.0.0
Published
A NestJS decorator to prevent duplicate execution of scheduled tasks
Maintainers
Readme
nest-queue-batch
A NestJS decorator to prevent duplicate execution of scheduled tasks (cron jobs).
When a scheduled task takes longer than its interval, multiple instances can run simultaneously, causing race conditions and duplicate processing. This library provides a simple decorator-based solution to ensure only one instance of a task runs at a time.
Installation
npm install nest-queue-batchQuick Start
1. Import the Module
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { QueueBatchModule } from 'nest-queue-batch';
@Module({
imports: [
ScheduleModule.forRoot(),
QueueBatchModule,
],
})
export class AppModule {}2. Use the Decorator
import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { QueueBatch } from 'nest-queue-batch';
@Injectable()
export class TaskService {
@Cron('*/5 * * * * *')
@QueueBatch()
async handleCron() {
// This method will not run concurrently
await this.longRunningTask();
}
}Usage Examples
Basic Usage (Skip Mode)
By default, if a task is already running, subsequent calls are skipped:
@Cron('*/5 * * * * *')
@QueueBatch()
async processOrders() {
// If this takes longer than 5 seconds,
// the next scheduled call will be skipped
await this.processAllPendingOrders();
}Custom Key
Use a custom key to share locks between different methods:
@Cron('0 * * * * *')
@QueueBatch({ key: 'order-processing' })
async processNewOrders() {
await this.handleNewOrders();
}
@Cron('30 * * * * *')
@QueueBatch({ key: 'order-processing' })
async processFailedOrders() {
// This won't run if processNewOrders is still running
await this.retryFailedOrders();
}Custom Timeout
Set a timeout to auto-release the lock if a task hangs:
@Cron('0 */5 * * * *')
@QueueBatch({ timeout: 30000 }) // 30 seconds timeout
async syncData() {
// Lock will be auto-released after 30 seconds
// even if the task hasn't completed
await this.syncExternalData();
}Wait for Completion Mode
Instead of skipping, wait for the previous task to complete:
@Cron('*/10 * * * * *')
@QueueBatch({ waitForCompletion: true })
async criticalTask() {
// If previous execution is running,
// this will wait and then execute
await this.processImportantData();
}Combined Options
@Cron('0 * * * * *')
@QueueBatch({
key: 'data-sync',
timeout: 120000, // 2 minutes timeout
waitForCompletion: true // Queue instead of skip
})
async syncAllData() {
await this.fullDataSync();
}API Reference
@QueueBatch(options?: QueueBatchOptions)
A method decorator that prevents duplicate execution of the decorated method.
Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| key | string | ClassName_MethodName | Unique identifier for the lock. Methods with the same key share the same lock. |
| timeout | number | 60000 | Lock timeout in milliseconds. The lock is automatically released after this duration to prevent deadlocks. |
| waitForCompletion | boolean | false | When true, queued calls wait for the current execution to complete before running. When false, queued calls are skipped. |
QueueBatchService
You can also inject QueueBatchService to manually manage locks:
import { Injectable } from '@nestjs/common';
import { QueueBatchService } from 'nest-queue-batch';
@Injectable()
export class MyService {
constructor(private readonly queueBatchService: QueueBatchService) {}
async customLockLogic() {
const key = 'my-custom-lock';
if (this.queueBatchService.tryAcquire(key, 60000)) {
try {
await this.doWork();
} finally {
this.queueBatchService.release(key);
}
}
}
}Methods
tryAcquire(key: string, timeout: number): boolean- Attempts to acquire a lock. Returnstrueif successful.waitAndAcquire(key: string, timeout: number): Promise<boolean>- Waits for the lock to be available, then acquires it.release(key: string): void- Releases the lock.isLocked(key: string): boolean- Checks if a lock is currently held.
How It Works
- When a decorated method is called, the decorator attempts to acquire a lock using the specified key.
- If the lock is acquired, the method executes normally. Upon completion (or error), the lock is released.
- If the lock cannot be acquired (another execution is in progress):
- Skip mode (
waitForCompletion: false): The method call returns immediately without executing. - Wait mode (
waitForCompletion: true): The call is queued and will execute once the lock becomes available.
- Skip mode (
- If the timeout expires before the method completes, the lock is automatically released to prevent deadlocks.
Requirements
- NestJS 9.x or 10.x
- @nestjs/schedule 3.x or 4.x
License
MIT
