Useful Utilities
CommandKit provides a collection of essential utilities that help you manage common programming challenges like controlling how many requests can be made, ensuring only one operation accesses a resource at a time, and managing multiple tasks efficiently.
Available Utilities
Rate Limiter
Think of rate limiting like a traffic light for your application. It controls how often something can happen - like how many times a user can click a button or make an API request. This prevents your system from being overwhelmed.
import { ratelimit } from 'commandkit/ratelimit';
const allowed = await ratelimit('user:123');
if (allowed) {
// Process the request
}
Mutex
A mutex is like a "do not disturb" sign for your data. It ensures that only one operation can access a shared resource at a time. Imagine multiple people trying to edit the same document - a mutex makes sure only one person can edit it at once.
import { withMutex } from 'commandkit/mutex';
const result = await withMutex('shared-resource', async () => {
return await updateSharedResource();
});
Semaphore
A semaphore is like a parking lot with a limited number of spaces. It allows a specific number of operations to happen at the same time, but no more. Perfect for controlling access to limited resources like database connections.
import { withPermit } from 'commandkit/semaphore';
const result = await withPermit('database-connection', async () => {
return await executeDatabaseQuery();
});
Async Queue
An async queue is like a line at a bank. Tasks wait in line and get processed one by one (or a few at a time). This helps you control how many things happen simultaneously and ensures everything gets done in an orderly way.
import { createAsyncQueue } from 'commandkit/async-queue';
const queue = createAsyncQueue({ concurrency: 3 });
const result = await queue.add(async () => await processTask());
Common Patterns
Cancelling Operations
All utilities support cancellation, which is like having an emergency stop button. You can cancel operations if they take too long or if you need to stop them for any reason.
// Create a timeout that cancels after 5 seconds
const signal = AbortSignal.timeout(5000);
// Rate limiting with timeout
const allowed = await ratelimit('user:123');
// Mutex with timeout
const result = await withMutex(
'resource',
async () => {
return await criticalOperation();
},
30000,
signal,
);
// Semaphore with timeout
const result = await withPermit(
'resource',
async () => {
return await limitedOperation();
},
30000,
signal,
);
// Queue with timeout
const queue = createAsyncQueue({ signal });
const result = await queue.add(async () => await task());
Using External Storage
All utilities support custom storage implementations for distributed environments:
// Redis storage implementations are available from @commandkit/redis
import {
RedisRateLimitStorage,
RedisMutexStorage,
RedisSemaphoreStorage,
} from '@commandkit/redis';
import { Redis } from 'ioredis';
const redis = new Redis();
// Rate limiting with Redis
const rateLimiter = createRateLimiter({
storage: new RedisRateLimitStorage(redis),
});
// Mutex with Redis
const mutex = createMutex({
storage: new RedisMutexStorage(redis),
});
// Semaphore with Redis
const semaphore = createSemaphore({
storage: new RedisSemaphoreStorage(redis),
});
Handling Errors Gracefully
When things go wrong, it's important to handle errors properly so your application doesn't crash and can recover gracefully.
try {
const result = await withMutex('resource', async () => {
return await criticalOperation();
});
} catch (error) {
if (error.message.includes('aborted')) {
console.log('Operation was cancelled');
} else if (error.message.includes('timeout')) {
console.log('Operation took too long');
}
}
When to Use Each Utility
Utility | What it does | When to use it |
---|---|---|
Rate Limiter | Controls how often something can happen | API endpoints, user actions, preventing spam |
Mutex | Ensures only one thing can access a resource | Database transactions, file operations, config updates |
Semaphore | Limits how many things can happen at once | Database connections, API concurrency, resource pools |
Async Queue | Processes tasks in an orderly way | Batch operations, file processing, email sending |
Best Practices
-
Choose the right tool: Think about what you're trying to achieve:
- Need to limit how often something happens? Use a rate limiter
- Need to ensure only one thing accesses a resource? Use a mutex
- Need to limit how many things happen at once? Use a semaphore
- Need to process tasks in order? Use a queue
-
Set reasonable timeouts: Always set timeouts to prevent your application from hanging forever if something goes wrong.
-
Use descriptive names: Give your resources meaningful names so you can easily debug issues later.
-
Handle errors: Always handle errors properly so your application can recover from problems.
-
Consider your setup: If you're running multiple instances of your app, use external storage like Redis.
-
Monitor usage: Keep an eye on how these utilities are being used to optimize performance.
Next Steps
Ready to learn more? Check out the detailed guides for each utility: