Mapping Over Promises in JavaScript

Mapping Over Promises in JavaScript

ยท

4 min read

Hey there, did you recently run into the issue where you expected the result of your map function to return a resolved/awaited value? For example:

const result = ids.map(async (id) => {
  return await fetchData(id);
});

//expected: [{name: "Adam"}, {name: "Linda"}]
//actual result: [Promise {<pending>}, Promise {<pending>}, ...]

You're not alone. I've experienced it and seen it a few times during pair programming. This blog post will show how to map over async/await operations.

Ready? Let's go.

The Problem

Picture this: you're handed an array of items needing asynchronous processing. Maybe these items represent IDs and you need to fetch data for each. Here's a common mistake people make:

const ids = [1, 2, 3, 4, 5];

const fetchData = async (id) => {
  // Simulates an async fetch operation
  return `data for ${id}`;
};

const processItems = async () => {
  const result = ids.map(async (id) => {
    return await fetchData(id);
  });

  console.log(result); // [Promise {<pending>}, Promise {<pending>}, ...]
};

processItems();

Okay, so what's going wrong here? ๐Ÿค” The map function is returning an array of promises and not waiting for them to resolve. This is not what we want and can lead to chaos when trying to use the result later.

The for...of Loop Solution

One approach is to use the for...of loop. This is useful if your async operations need to happen in sequence rather than executing in parallel.

const processItemsSequentially = async () => {
  const result = [];
  for (const id of ids) {
    const data = await fetchData(id);
    result.push(data);
  }

  console.log(result); // ['data for 1', 'data for 2', ...]
};

processItemsSequentially();

This is easier to read and understand if you're aiming for sequential execution, but be careful, this approach could be slower because each operation waits for the previous one to complete.

The Promise.all Solution

Let's clean this up using Promise.all and Array.prototype.map(). This nifty method can take our array of promises and return a single promise that resolves when all have resolved.

const processItems = async () => {
  const result = await Promise.all(ids.map((id) => {
    return fetchData(id);
  }));

  console.log(result); // ['data for 1', 'data for 2', ...]
};

processItems();

Boom! Now we're cooking with gas. The array of promises is wrapped in a single promise, which resolves with the result. Much better, and runs concurrently! However, there's a problem. Running promises concurrently (e.g. with 1,000 items) does not always mean fast, it can become slow and could lead to memory problems.

Hint: you can choose to use promise.all() or promise.allSettled() with this example.

The Cleaner Solution with p-map

Finally, let's look at a better way to map concurrently while limiting how many promises should run concurrently. For this, we're going to use the p-map from npm. You can add it to your project using npm install p-map.

It is different from Promise.all() in that you can control the concurrency limit and decide whether or not to stop iterating when there's an error. Here's how the processItems() function we defined would look:

import pMap from "p-map";
const ids = [1, 2, 3, 4, 5];

const fetchData = async (id: number) => {
  // Simulates an async fetch operation
  return `data for ${id}`;
};

const processItems = async () => {
  const result = await pMap(ids, 
    (id) => fetchData(id), { concurrency: 2 });

  console.log(result); // ['data for 1', 'data for 2', ...]
};

processItems();

Although we used a different syntax here, this version is concise and effective. By setting a concurrency limit, this pattern avoids overloading the system when there's a lot of data and we can control if we want to stop or continue when there's an error. For more options with p-map, check out the documentation on GitHub.

Conclusion

There you have it, folks! We explored a common mistake when mapping over promises and covered three effective solutions:

  1. Using for...of for sequential operations ๐ŸŒ€

  2. Using Promise.all for parallel execution ๐Ÿ‘Œ๐Ÿฝ

  3. A cleaner solution using the p-map module ๐ŸŒŸ

I hope this helps you become a promise-mapping pro! Happy coding! ๐ŸŽ‰

Feel free to drop any questions or examples youโ€™ve come across in the comments below. Until next time, all the best in your JavaScript journey!


Do you also enjoy similar topics but in video format? Follow me on YouTube for more lessons on JavaScript and web development!

Originally authored and published on Telerikโ€™s blog by me.

Did you find this article valuable?

Support Peter's Blog by becoming a sponsor. Any amount is appreciated!

ย