Getting Started
Async Lib is a powerful Salesforce Apex framework that provides an elegant solution for managing asynchronous processes. It eliminates common limitations like "Too many queueable jobs" errors and offers a unified API for queueable, batchable, and schedulable jobs.
Why Async Lib?
Salesforce Limits
| Apex Context | Queueable | Future | Batch | Schedule |
|---|---|---|---|---|
| Synchronous or Scheduled* process | 50 | 50 | 100 in Holding status 5 in Queued or Active status | 100 |
| Queueable job | 1 | 50 | ||
| @future method call | 1 | 0 | ||
| Batch job | 1 | 0 | As above in finish() batch method. For start() and execute() methods, the limit is 0. |
Key Benefits
- 🚀 Eliminates Queueable Limits: Automatically handles "Too many queueable jobs" by intelligent chaining and batch overflow
- 🎯 Unified API: Single, consistent interface for all async job types (Queueable, Batchable, Schedulable)
- ⚡ Smart Prioritization: Jobs execute based on priority with automatic sorting
- 🛡️ Advanced Error Handling: Built-in error recovery, rollback options, and continuation strategies
- 📊 Job Tracking: Comprehensive tracking with custom job IDs and result records
- ⚙️ Configuration-Driven: Control job behavior through custom metadata without code changes
- 🔗 Support Finalizers: Execute cleanup logic after job completion with full context
Core Concepts
1. QueueableJob Base Class
All queueable jobs extend the QueueableJob abstract class:
public class MyQueueableJob extends QueueableJob {
public override void work() {
// Your business logic here
System.debug(
'Processing job: ' + Async.getQueueableJobContext().currentJob.customJobId
);
}
}2. Builder Pattern API
All job types use a fluent builder pattern:
// Queueable Job
Async.queueable(new MyQueueableJob())
.priority(10)
.delay(5)
.enqueue();
// Batch Job
Async.batchable(new MyBatchJob())
.scopeSize(100)
.execute();
// Schedulable Job
Async.schedulable(new MySchedulableJob())
.name('Daily Cleanup')
.cronExpression('0 0 2 * * ? *')
.skipWhenAlreadyScheduled()
.schedule();3. Automatic Job Chaining
When queueable limits are reached, Async Lib automatically switches to scheduled-batch-based execution, ensuring your jobs always run without hitting Queueable platform limits.
Your First Queueable Job
Let's create a simple job that processes accounts:
Step 1: Create Your Job Class
public class AccountProcessorJob extends QueueableJob {
private List<Id> accountIds;
public AccountProcessorJob(List<Id> accountIds) {
this.accountIds = accountIds;
}
public override void work() {
// Get job context
Async.QueueableJobContext ctx = Async.getQueueableJobContext();
System.debug('Processing job: ' + ctx.currentJob.customJobId);
// Process accounts
List<Account> accounts = [
SELECT Id, Name
FROM Account
WHERE Id IN :accountIds
];
for (Account acc : accounts) {
acc.Description = 'Processed by ' + ctx.currentJob.className;
}
update accounts;
System.debug('Processed ' + accounts.size() + ' accounts');
}
}Step 2: Enqueue the Job
// Get some account IDs
List<Id> accountIds = new List<Id>{
'0013000000abcdef',
'0013000000ghijkl'
};
// Enqueue the job
Async.Result result = Async.queueable(new AccountProcessorJob(accountIds))
.priority(5)
.enqueue();
System.debug('Job enqueued with ID: ' + result.customJobId);Error Handling
Async Lib provides sophisticated error handling options:
Async.queueable(new MyJob())
.continueOnJobEnqueueFail() // Don't fail if enqueue fails
.continueOnJobExecuteFail() // Continue processing other jobs if this fails
.rollbackOnJobExecuteFail() // Rollback any DML if job fails
.enqueue();Using Finalizers
Finalizers run after job completion and have access to success/failure context:
public class MyJobFinalizer extends QueueableJob.Finalizer {
public override void work() {
Async.QueueableJobContext ctx = Async.getQueueableJobContext();
FinalizerContext finalizerCtx = ctx.finalizerCtx;
if (finalizerCtx.getResult() == ParentJobResult.SUCCESS) {
System.debug('Job completed successfully!');
} else {
System.debug('Job failed: ' + finalizerCtx.getException().getMessage());
}
}
}
// Attach finalizer within a job
public class MyMainJob extends QueueableJob {
public override void work() {
// Do main work...
// Attach finalizer
Async.queueable(new MyJobFinalizer())
.attachFinalizer();
}
}Configuration
Control job behavior using Custom Metadata (QueueableJobSetting__mdt):
- Go to Setup → Custom Metadata Types → QueueableJobSetting → Manage Records
- Create or edit settings:
- All: Global settings for all jobs
- Specific Class Name: Settings for specific job classes
Available settings:
- QueueableJobName__c: The name of the QueueableJob.
- IsDisabled__c: Disable job execution
- CreateResult__c: Create
AsyncResult__crecords for tracking
Async Result Records
When enabled in QueueableJobSetting__mdt (CreateResult__c = true), Async Lib automatically creates an AsyncResult__c record for every job in a chain — including jobs that were skipped — so you can see exactly what happened, and why. Query by ChainId__c to see a whole chain run as a timeline.
Key Fields
- ChainId__c: Correlation id shared by every job in the same chain run
- Status__c: Lifecycle outcome —
COMPLETED,FAILED,SKIPPED_DEPENDENCY,SKIPPED_CHAIN_STOPPED,SKIPPED_EXPLICIT,SKIPPED_DISABLED - CustomJobId__c: Unique job ID generated by Async Lib
- SalesforceJobId__c: Underlying Salesforce job ID (Queueable, Batch, or Scheduled)
- ClassName__c: Apex class name of the job
- Result__c: Platform job result for jobs that ran (
SUCCESS,UNHANDLED_EXCEPTION) - ExceptionType__c / ExceptionMessage__c: Failure detail when
Status__cisFAILED - SkipReason__c: Why a skipped job was skipped (e.g. "Dependency 'extract' required SUCCESS but was FAILURE")
- DependsOnResult__c: Self-lookup to the result of the dependency that decided this job's fate, with RequiredOutcome__c / ActualOutcome__c
- RetryAttempts__c: How many retries the job went through
- RetryHistory__c: Per-attempt retry log
These records are never deleted automatically. See AsyncResult Cleanup for the scheduled cleanup batch with separate retention for failed results and the rest.
What's Next?
Now that you understand the basics:
- Standard Apex vs Async Lib - See each plain-Apex async pattern next to its Async Lib equivalent.
- Explore the API - Learn about all available methods and options:
- Queueable API - Detailed information on using Queueable jobs
- Batchable API - Detailed information on using Batchable jobs
- Schedulable API - Detailed information on using Schedulable jobs
- Read the Blog Post - Check out the detailed explanation: Apex Queueable Processing Framework
- Initial Queueable Chain Schedulable Explanation - Learn why this job is important for framework to function properly.
Quick Tips
- Job Naming: Jobs get unique names with timestamps:
MyJob::2024-01-15T10:30:45.123Z::1 - Custom Job IDs: Every job gets a UUID for tracking independent of Salesforce Job IDs
- Priority Matters: Lower numbers = higher priority. Finalizers always run first.
- Test Friendly: Framework handles test context automatically
- Callouts Supported: Use
QueueableJob.AllowsCalloutsfor HTTP callouts
