Map Async




Prompt
Implement an asyncMap
function that transforms each element in an array using an asynchronous mapping function.
Unlike the standard Array.prototype.map()
that works with synchronous operations, your function should handle asynchronous transformations and wait for all of them to complete.
The function should return a Promise which resolves to the mapped results.
Example
const asyncTriple = async (x) => {
await new Promise(resolve => setTimeout(resolve, 100));
return x * 3;
};
const tripled = await asyncMap([1, 2, 3], asyncTriple);
console.log(tripled); // [3, 6, 9]
Playground
Consider using Promise.all()
to wait for all the asynchronous operations to complete. This method takes an array of promises and returns a single promise that resolves when all promises in the array have resolved.
Remember that the order of results matters. Even though async operations may complete in any order, your function should ensure the results maintain the same order as the input array.
Be careful with error handling! If any of the individual operations fail, you need to decide whether to fail the entire mapping operation or handle individual errors more gracefully.
Solution
Explanation
The asyncMap
function is a powerful tool for handling asynchronous operations on arrays. It's similar to the regular Array.prototype.map()
method, but with the crucial difference that it works with asynchronous callback functions and returns a promise that resolves once all the mapping operations are complete.
Let's break down how our solution works.
Core Implementation
Our implementation of asyncMap
uses a custom version of Promise.all
to handle the parallel asynchronous operations:
async function asyncMap(arr, asyncFn) {
// Create an array of promises by applying the async function to each element
const promises = arr.map(item => asyncFn(item));
// Use our customPromiseAll instead of the native Promise.all
return customPromiseAll(promises);
}
Here's what's happening step by step:
-
First, we create an array of promises by applying the async callback function to each element in the input array using the regular
map
method. This doesn't wait for the promises to resolve - it just collects them into an array. -
Then, instead of using the native
Promise.all()
, we use our custom implementationcustomPromiseAll()
to wait for all these promises to resolve. -
Finally, we return the result of
customPromiseAll()
, which will be an array containing the resolved values from each promise, in the same order as the original array.
The customPromiseAll Implementation
Let's examine how our customPromiseAll
function works:
function customPromiseAll(promises) {
const results = [];
let completedPromisesCount = 0;
return new Promise((resolve, reject) => {
// Handle empty array case
if (promises.length === 0) {
resolve(results);
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
results[index] = value;
completedPromisesCount++;
if (completedPromisesCount === promises.length) {
resolve(results);
}
})
.catch(reject);
});
});
}
This implementation:
- Creates a new Promise that we'll manually resolve or reject.
- Initializes an array to store results.
- Uses a counter to track how many operations have completed.
- Handles the empty array case immediately.
- For each promise in the array:
- Ensures we handle both Promise and non-Promise values with
Promise.resolve()
- When a promise resolves, stores the result at the correct index to maintain order
- Increments the completion counter
- If all promises have resolved, resolves the main promise with the results array
- If any promise rejects, rejects the main promise with that error
- Ensures we handle both Promise and non-Promise values with
This approach ensures parallel execution, order preservation, and proper error handling.
By combining asyncMap
with our customPromiseAll
implementation, we have a robust solution for transforming arrays with asynchronous operations that works even in environments where Promise.all
might not be available.