EN/CH Mode
BRUCE_FE JS Interview Notes - Implementing Promise Pool Concurrency Control
Learn how to implement Promise Pool to control concurrent asynchronous tasks, solve high concurrency issues, and enhance frontend application performance and interview competitiveness.
EN/CH Mode
Lazy to read articles? Then watch videos!
Promise Pool Overview
Promise Pool is a technique for controlling concurrent Promise execution, primarily used to limit the number of simultaneously executing asynchronous tasks. When handling large numbers of asynchronous operations (such as API requests, file processing, etc.), it can effectively prevent system overload and optimize resource usage.
Main Characteristics of Promise Pool
- 1.
Control Concurrency
: Limit the number of simultaneously executing Promises - 2.
Dynamic Scheduling
: Automatically start the next task when one completes - 3.
Sequential Submission
: Tasks are submitted in order but completion order is not guaranteed - 4.
Error Handling
: Can handle individual task failures without affecting other tasks - 5.
Resource Optimization
: Prevents excessive system resource consumption
Promise Pool Operation Principle Diagram
Task Queue: [Task1, Task2, Task3, Task4, Task5, Task6, ...]
│
▼
┌───────────────────────────────────┐
│ Promise Pool (Concurrency=3) │
├───────────┬───────────┬───────────┤
│ Task1 │ Task2 │ Task3 │ ← Simultaneously executing tasks
│ Running │ Running │ Running │
├───────────┴───────────┴───────────┤
│ │
│ Task4, Task5, ... Waiting │
│ │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ Promise Pool (Concurrency=3) │
├───────────┬───────────┬───────────┤
│ Task4 │ Task2 │ Task3 │ ← After Task1 completes,
│ Running │ Running │ Running │ Task4 starts executing
└───────────┴───────────┴───────────┘
When the concurrency is set to 3, the system executes at most 3 tasks at once. When one task completes, the pool immediately takes the next task from the queue to start executing, maintaining a constant concurrency level until all tasks are completed.
In frontend interviews, implementing a simple Promise Pool is a good topic to test candidates' understanding of Promises, asynchronous processing, and execution control.
Basic Implementation: Promise Pool
Below is a basic Promise Pool implementation that limits the number of simultaneously running Promises:
const promisePool = async function(functions, n) {
return new Promise((resolve) => {
let inProgress = 0; // Number of Promises currently executing
let functionIndex = 0; // Index of function to execute next
// Define helper function to execute next function
function helper() {
// If all functions are completed, resolve the Promise
if (functionIndex >= functions.length && inProgress === 0) {
resolve();
return;
}
// When there's available capacity and functions to execute, execute next
while (inProgress < n && functionIndex < functions.length) {
// Get next function
const fn = functions[functionIndex++];
inProgress++; // Increase number of executing Promises
// Execute function
fn().then(() => {
inProgress--; // Decrease number of executing Promises
helper(); // Try to execute next
});
}
}
// Start execution
helper();
});
};
Example of using this Promise Pool:
// Function to simulate asynchronous tasks
const createTask = (id, delay) => {
return () => new Promise(resolve => {
console.log(`Task ${id} started, expected duration ${delay}ms`);
setTimeout(() => {
console.log(`Task ${id} completed`);
resolve(`Result of task ${id}`);
}, delay);
});
};
// Create a series of tasks
const tasks = [
createTask(1, 1000),
createTask(2, 500),
createTask(3, 2000),
createTask(4, 800),
createTask(5, 1500),
createTask(6, 1000),
createTask(7, 600),
createTask(8, 3000)
];
// Use Promise Pool with maximum concurrency of 3
promisePool(tasks, 3).then(() => {
console.log('All tasks completed');
});
// Output (timing for reference only):
// Task 1 started, expected duration 1000ms
// Task 2 started, expected duration 500ms
// Task 3 started, expected duration 2000ms
// Task 2 completed
// Task 4 started, expected duration 800ms
// Task 1 completed
// Task 5 started, expected duration 1500ms
// Task 4 completed
// Task 6 started, expected duration 1000ms
// ...
// All tasks completed
Practical Application Scenarios
Promise Pool has many practical application scenarios in frontend development:
1. Batch File Upload
// Batch file upload example
async function uploadFilesWithLimit(files, maxConcurrent) {
const uploadTasks = files.map(file => {
return async () => {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`Upload failed for ${file.name}`);
}
return await response.json();
};
});
return promisePoolWithResults(uploadTasks, maxConcurrent);
}
// Usage example
document.getElementById('fileInput').addEventListener('change', async (event) => {
const files = event.target.files;
try {
const results = await uploadFilesWithLimit(Array.from(files), 3);
console.log('All files uploaded successfully', results);
} catch (error) {
console.error('Error occurred during upload', error);
}
});
2. Paginated Data Batch Processing
// Paginated data batch processing example
async function processAllPages(totalPages, maxConcurrent) {
// Create page processing tasks
const pageTasks = Array.from({ length: totalPages }, (_, i) => {
const page = i + 1;
return async () => {
console.log(`Processing page ${page}`);
const response = await fetch(`/api/data?page=${page}`);
if (!response.ok) {
throw new Error(`Failed to fetch page ${page}`);
}
const data = await response.json();
// Process the data...
return processPagesData(data);
};
});
// Use Promise Pool to limit concurrency
return promisePoolWithResults(pageTasks, maxConcurrent);
}
// Usage example
processAllPages(100, 5)
.then(results => {
console.log('All pages processed');
// Merge or further process results...
})
.catch(error => {
console.error('Error occurred during processing', error);
});