Job Convenience Methods
Sidequest.js provides several convenience methods that can be used inside your job's run
method to control job execution flow and trigger specific state transitions. These methods help you handle different scenarios like manual completion, failures, retries, and delays.
Overview
The convenience methods available in job classes are:
complete(result)
- Mark the job as completed with a specific resultfail(reason)
- Mark the job as permanently failedretry(reason, delay?)
- Force a retry with optional delaysnooze(delay)
- Delay job execution for a specified time
IMPORTANT
You must return the result of any convenience method to trigger the job transition. Simply calling them without returning their result will do absolutely nothing.
// ❌ Wrong - this does nothing
this.fail("Something went wrong");
// ✅ Correct - this marks the job as failed indefinitely
return this.fail("Something went wrong");
complete(result)
Method
Explicitly mark a job as completed with a specific result. This is useful when you want to control exactly what gets stored as the job result. This is also optional, as jobs will automatically complete when the run
method finishes without throwing an error and store anything returned.
Complete Method Signature
complete(result: unknown): CompletedResult
Complete Method Parameters
result
- The result to store in the job'sresult
field. This can be any serializable value.
Complete Method Examples
export class DataValidationJob extends Job {
async run(data: any) {
if (this.isValid(data)) {
// Explicitly complete with validation details
return this.complete({
valid: true,
data: data,
validatedAt: new Date(),
validator: "v2.1.0",
});
}
return this.fail("Data validation failed");
}
}
export class ConditionalJob extends Job {
async run(condition: string) {
switch (condition) {
case "process":
const result = await this.processData();
return this.complete(result);
case "skip":
return this.complete({ skipped: true, reason: "Condition not met" });
default:
return this.fail(`Unknown condition: ${condition}`);
}
}
}
fail(reason)
Method
Mark a job as permanently failed, bypassing any remaining retry attempts. The job will move directly to the failed
state.
Fail Method Signature
fail(reason: string | Error): FailedResult
Fail Method Parameters
reason
- The reason for the failure, can be a string message or an Error object. This will be stored in the job'serrors
field.
Fail Method Examples
export class UserProcessingJob extends Job {
async run(userId: number) {
const user = await this.getUser(userId);
if (!user) {
// Permanent failure - user doesn't exist
return this.fail(`User ${userId} not found`);
}
if (user.isDeleted) {
// Permanent failure - user is deleted
return this.fail(`User ${userId} has been deleted`);
}
// Continue processing...
return this.processUser(user);
}
}
export class FileProcessingJob extends Job {
async run(filePath: string) {
try {
const fileExists = await this.checkFileExists(filePath);
if (!fileExists) {
// File doesn't exist - no point retrying
return this.fail(new Error(`File not found: ${filePath}`));
}
const content = await this.readFile(filePath);
if (!this.isValidFormat(content)) {
// Invalid format - won't get better on retry
return this.fail("File format is invalid and cannot be processed");
}
return this.processFile(content);
} catch (error) {
return this.fail(`Unexpected error while processing file: ${error.message}`);
}
}
}
retry(reason, delay?)
Method
Force a job retry with a custom reason and optional delay. This is useful when you want to retry due to specific conditions rather than thrown exceptions.
Retry Method Signature
retry(reason: string | Error, delay?: number): RetryResult
Retry Method Parameters
reason
- The reason for the retry (string or Error object)delay
- Optional delay in milliseconds before the retry
Retry Method Examples
export class ExternalServiceJob extends Job {
async run(apiEndpoint: string) {
try {
const response = await fetch(apiEndpoint);
if (response.status === 429) {
// Rate limited - retry after delay
const retryAfter = response.headers.get("Retry-After");
const delay = retryAfter ? parseInt(retryAfter) * 1000 : 60000;
return this.retry(`Rate limited by API (HTTP 429)`, delay);
}
if (!response.ok) {
// Client error - probably permanent
return this.fail(`API error: ${response.status} ${response.statusText}`);
}
const data = await response.json();
return { success: true, data };
} catch (error) {
// Network errors - let normal retry mechanism handle
throw error;
}
}
}
export class ServiceDependencyJob extends Job {
async run(serviceId: string) {
const serviceStatus = await this.checkServiceStatus(serviceId);
if (serviceStatus === "maintenance") {
// Service in maintenance - retry in 15 minutes
return this.retry("Service is in maintenance mode", 15 * 60 * 1000);
}
if (serviceStatus === "degraded") {
// Service degraded - retry sooner
return this.retry("Service performance is degraded", 2 * 60 * 1000);
}
if (serviceStatus !== "operational") {
return this.fail(`Service is in unknown state: ${serviceStatus}`);
}
// Service is operational, proceed
return this.callService(serviceId);
}
}
snooze(delay)
Method
Delay job execution for a specified amount of time. The job returns to the waiting
state and becomes available again after the delay period.
Snooze Method Signature
snooze(delay: number): SnoozeResult
Snooze Method Parameters
delay
- Delay in milliseconds
Snooze Method Examples
export class ScheduledTaskJob extends Job {
async run(scheduledTime: string) {
const targetTime = new Date(scheduledTime);
const now = new Date();
if (now < targetTime) {
// Not time yet - snooze until scheduled time
const delay = targetTime.getTime() - now.getTime();
return this.snooze(delay);
}
// It's time to execute
return this.executeScheduledTask();
}
}
export class BusinessHoursJob extends Job {
async run(data: any) {
if (!this.isBusinessHours()) {
// Wait until next business day at 9 AM
const nextBusinessDay = this.getNextBusinessDay();
const delay = nextBusinessDay.getTime() - Date.now();
return this.snooze(delay);
}
// Execute during business hours
return this.processBusinessData(data);
}
private isBusinessHours(): boolean {
const now = new Date();
const hour = now.getHours();
const day = now.getDay();
// Monday-Friday, 9 AM - 5 PM
return day >= 1 && day <= 5 && hour >= 9 && hour < 17;
}
private getNextBusinessDay(): Date {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(9, 0, 0, 0);
// Skip weekends
while (tomorrow.getDay() === 0 || tomorrow.getDay() === 6) {
tomorrow.setDate(tomorrow.getDate() + 1);
}
return tomorrow;
}
}
Best Practices
- Use appropriate methods: Choose the right convenience method for your situation
fail()
for permanent, unrecoverable errorsretry()
for transient issues that might resolvesnooze()
for timing-based delayscomplete()
for explicit control over results
- Always return the result: Never call convenience methods without returning their result
- Provide meaningful reasons: Include descriptive error messages and reasons
- Use reasonable delays: Don't retry too aggressively - respect external services
- Combine with try-catch: Use convenience methods for controlled flow, exceptions for unexpected errors