frontend
Throttle Promises in JavaScript
January 24, 2026
Throttle Promises in JavaScript
Overview
Throttling promises is a technique to limit the number of concurrent promise executions. This is essential for managing API rate limits, preventing resource exhaustion, and controlling the flow of asynchronous operations.
Basic Implementation
function throttlePromises(promises, maxRequests) {
const results = [];
let running = 0;
let index = 0;
return new Promise((resolve, reject) => {
function runNext() {
// Check if all promises have been processed
if (index >= promises.length) {
// Resolve the promise with the final results once all promises are processed
if (running === 0) {
resolve(results);
}
return;
}
// Get the next promise from the array
const promise = promises[index];
index++;
// Increment the running count to track the number of currently running promises
running++;
// Execute the promise
promise()
.then((result) => {
// Push the result of the promise to the results array
results.push(result);
})
.catch((error) => {
// Handle errors if needed
reject(error);
})
.finally(() => {
// Decrement the running count when the promise is settled
running--;
// Run the next promise
runNext();
});
// If there are available slots for execution, immediately run the next promise
if (running < maxRequests) {
runNext();
}
}
// Start processing promises
runNext();
});
}
Example Usage
// Define some promise-generating functions (simulating API requests)
function simulateAPIRequest(id) {
return () =>
new Promise((resolve) => {
console.log(`Sending request for ID: ${id}`);
setTimeout(() => {
console.log(`Received response for ID: ${id}`);
resolve(`Response for ID: ${id}`);
}, 1000); // Simulate API delay
});
}
// Create an array of promise-generating functions
const promises = [
simulateAPIRequest(1),
simulateAPIRequest(2),
simulateAPIRequest(3),
simulateAPIRequest(4),
simulateAPIRequest(5),
simulateAPIRequest(6),
simulateAPIRequest(7),
simulateAPIRequest(8),
simulateAPIRequest(9),
simulateAPIRequest(10),
];
// Throttle the promises to limit to 3 requests at a time
throttlePromises(promises, 3)
.then((results) => {
console.log("All requests completed:", results);
})
.catch((error) => {
console.error("Error:", error);
});
Advanced Implementation with Error Handling
function throttlePromises(promises, maxRequests, options = {}) {
const {
onProgress = null,
stopOnError = false,
retryOnError = false,
maxRetries = 0
} = options;
const results = [];
const errors = [];
let running = 0;
let index = 0;
let completed = 0;
return new Promise((resolve, reject) => {
function runNext() {
if (index >= promises.length) {
if (running === 0) {
if (stopOnError && errors.length > 0) {
reject(errors[0]);
} else {
resolve({ results, errors });
}
}
return;
}
const promiseFn = promises[index];
index++;
running++;
function executePromise(retryCount = 0) {
promiseFn()
.then((result) => {
results.push({ index: completed, result });
completed++;
if (onProgress) {
onProgress(completed, promises.length);
}
})
.catch((error) => {
if (retryOnError && retryCount < maxRetries) {
// Retry the promise
executePromise(retryCount + 1);
return;
}
errors.push({ index: completed, error });
completed++;
if (stopOnError) {
reject(error);
return;
}
if (onProgress) {
onProgress(completed, promises.length);
}
})
.finally(() => {
running--;
runNext();
});
}
executePromise();
if (running < maxRequests) {
runNext();
}
}
runNext();
});
}
Use Cases
1. API Rate Limiting
function createAPIRequest(endpoint) {
return () => fetch(endpoint).then(res => res.json());
}
const endpoints = [
'/api/users/1',
'/api/users/2',
// ... 100 more endpoints
].map(createAPIRequest);
// Limit to 5 concurrent requests
throttlePromises(endpoints, 5)
.then(({ results }) => {
console.log('All users fetched:', results);
});
2. Image Processing
function processImage(imageUrl) {
return () => {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(processImageData(img));
img.src = imageUrl;
});
};
}
const imageUrls = [/* array of URLs */];
const processors = imageUrls.map(processImage);
throttlePromises(processors, 3)
.then(({ results }) => {
displayProcessedImages(results);
});
3. Database Batch Operations
function updateUser(userId, data) {
return () => db.update('users', userId, data);
}
const updates = users.map(user =>
updateUser(user.id, user.updatedData)
);
throttlePromises(updates, 10)
.then(({ results, errors }) => {
console.log(`Updated ${results.length} users`);
if (errors.length > 0) {
console.error(`${errors.length} updates failed`);
}
});
With Progress Tracking
throttlePromises(promises, 3, {
onProgress: (completed, total) => {
const percentage = (completed / total * 100).toFixed(1);
updateProgressBar(percentage);
console.log(`Progress: ${completed}/${total} (${percentage}%)`);
}
})
.then(({ results }) => {
console.log('All done!', results);
});
Comparison with Other Approaches
Sequential Execution
// Slow - one at a time
async function sequential(promises) {
const results = [];
for (const promiseFn of promises) {
results.push(await promiseFn());
}
return results;
}
Parallel Execution
// Fast but can overwhelm
async function parallel(promises) {
return Promise.all(promises.map(fn => fn()));
}
Throttled Execution
// Balanced - controlled concurrency
throttlePromises(promises, 5);
Advanced: With Priority Queue
class PriorityThrottle {
constructor(maxConcurrent) {
this.maxConcurrent = maxConcurrent;
this.running = 0;
this.queue = [];
}
add(promiseFn, priority = 0) {
return new Promise((resolve, reject) => {
this.queue.push({ promiseFn, priority, resolve, reject });
this.queue.sort((a, b) => b.priority - a.priority);
this.process();
});
}
async process() {
if (this.running >= this.maxConcurrent || this.queue.length === 0) {
return;
}
const { promiseFn, resolve, reject } = this.queue.shift();
this.running++;
try {
const result = await promiseFn();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.process();
}
}
}
Best Practices
- Choose Appropriate Limit: Based on API rate limits and server capacity
- Handle Errors: Don't let one failure stop all operations
- Track Progress: Provide feedback for long-running operations
- Monitor Performance: Track execution times and success rates
- Use for I/O Operations: Best for network/database calls
- Consider Retry Logic: Retry failed operations when appropriate
- Clean Up Resources: Ensure proper cleanup of resources
Real-World Example: Batch Upload
async function batchUpload(files, maxConcurrent = 3) {
const uploadPromises = files.map(file =>
() => uploadFile(file)
);
const { results, errors } = await throttlePromises(
uploadPromises,
maxConcurrent,
{
onProgress: (completed, total) => {
updateUploadProgress(completed, total);
},
stopOnError: false
}
);
return {
successful: results.length,
failed: errors.length,
results,
errors
};
}