Map Async

MediumGoogleUberLyftApple

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

Hint 1

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.

Hint 2

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.

Hint 3

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:

  1. 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.

  2. Then, instead of using the native Promise.all(), we use our custom implementation customPromiseAll() to wait for all these promises to resolve.

  3. 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

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.

00:00