When processing large datasets in JavaScript, we often need to perform asynchronous operations on multiple items. However, launching all operations simultaneously can overwhelm system resources or hit API rate limits. This is where the mapLimit function becomes invaluable.
mapLimit is a concurrency control pattern that applies an asynchronous function to an array of items while limiting how many operations run in parallel. It maintains the order of results to match the input array, regardless of when each operation completes.
Concurrency refers to multiple operations running simultaneously. With mapLimit, we specify exactly how many operations can run at once - not too many (overwhelming resources) and not too few (inefficient processing).
JavaScript promises are the foundation of this pattern. We track:
As operations complete, new ones begin until all items are processed. This creates a continuous flow without exceeding the concurrency limit.
async function mapLimit(items, limit, fn) { const promises = []; // Track all generated promises const results = Array(items.length); // Pre-allocate results array let activeCount = 0; // Track active operations let index = 0; // Next item to process const processNext = async () => { const currentIndex = index++; if (currentIndex >= items.length) { return; // No more items to process } activeCount++; try { // Process item and store result at original position results[currentIndex] = await fn(items[currentIndex], currentIndex, items); } catch (error) { // Preserve errors in results array results[currentIndex] = Promise.reject(error); } finally { activeCount--; promises.push(processNext()); // Process next item when this one finishes } }; // Initialize with batch of promises up to the limit while (activeCount < limit && index < items.length) { promises.push(processNext()); } // Wait for all processing to complete await Promise.all(promises); return results; }
Imagine a restaurant with a limited number of tables:
processNext) to handle the queue// Fetch data for multiple users with controlled concurrency async function fetchUserData(userIds) { return mapLimit(userIds, 5, async (userId) => { const response = await fetch(`/api/users/${userId}`); return response.json(); }); } // Process large file uploads with limited concurrency async function uploadFiles(files) { return mapLimit(files, 3, async (file) => { const formData = new FormData(); formData.append('file', file); const response = await fetch('/upload', { method: 'POST', body: formData }); return response.json(); }); }
By mastering mapLimit, you gain a powerful tool for handling asynchronous operations efficiently while respecting system constraints.
Test your understanding with 3 quick questions