The Power of Bluebird's Promise.map

Bluebird (or BlueBird.js) is a fully featured Javascript promises library. Whether you are using it in a Node.js environment or in the Browser, whether you are using plain JavaScript or TypeScript, Bluebird can make your life much easier when working with Promises.

I have to note that reactive programming using Observables (RxJs) is becoming more and more popular for asynchronous programming. Reactive extensions are an even more powerful tool but for a not so experienced developer it can be really overwhelming at the beginning. So if you are just learning to develop in JavaScript or TypeScript, you should familiarize yourself with promises first. They are part of the ES6 standard and with the entering of async/await in ES2017 async programming has become more convenient. Note that every RxJs Observable can easily be converted to a promise with toPromise().

That being said, the standard toolset of promises in Node.js or the browser is very limited. That's why libraries like Bluebird come in very handy.

One of the methods is provides really stands out is the map() method. The JavaScript Array class has a map() method, so this one is very similar. And of course also RxJs has a map operator. But it's the third parameter of Bluebird's map method, that gives you as a developer a lot of power.

With the third parameter you can specify the options for the map operation, and there is exactly one setting: concurrency

What does the official documentation say about the concurrency parameter?

"The concurrency limit applies to Promises returned by the mapper function and it basically limits the number of Promises created. For example, if concurrency is 3 and the mapper callback has been called enough so that there are three returned Promises currently pending, no further callbacks are called until one of the pending Promises resolves. So the mapper function will be called three times and it will be called again only after at least one of the Promises resolves."

So this means that there will never be more pending Promises than the concurrency you specified. If you pass an array of 1000 objects to the map() function and you specify a concurrency of 50, that means that the mapping function will be executing in parallel, but never more than 50 times.

Use Cases

What are the use cases for this?

  • You can use to basically spread the execution of a certain task over a longer period of time. If it's something that's transforming the objects of an array, or doing something with each object in the array.
  • You want to keep CPU usage low because something is running in the background and you want to keep more processing resources free for the "foreground task" (like one that is reacting on user input).
  • You want to throttle the calling of an external API, so not to overload it with requests and prevent of getting blocked, getting request timeouts, or something similar.

You see out-of-memory errors when using Promise.map() without a concurrency? Think about it... Promise.map() will execute the mapping function for all the objects in the array as soon as it can, so in the "worst" case it's running in parallel thousands of time which can easily lead to out-of-memory errors if you are doing some more complicated stuff inside the mapping function. With concurrency you can smooth out memory spikes and avoid crashes.

Example

Here's an example of how to use concurrency:

getAllPlayers() {
  // -- First, let's find all the teams
  return Teams.findAll().map(team => {
    // -- Now that we have the teams, load the players for each team from an external API
    return externalApi.getPlayersForTeam(team.id);
  }, {
    // -- 👇 Here is the concurrency option 
    concurrency: 5
  })
  .then(players => _.flatten(players));
}

This example code from the domain of sports is first loading all teams from the database, then mapping each team to find the players for the team from an external system (API). Then it's just flattening all the games using the (awesome) lodash library. So in order not to overburden the external API with too many requests at a time, concurrency is being used.

This is just one example of many things that you can do with Promise.map! What are your use cases for it?

Things to keep in mind

  • There is no guarantee on the order in which map calls the mapper function on the array elements. If you need sequential execution you should look into Promise.mapSeries() and Promise.each() - which are both very similar with a difference regarding the resolution value.

I appreciate your feedback, e.g. via Twitter or here in the comments - all further contact information can be found here.

Published: 2020-07-19 | Last update: 2020-07-19 | Tags: Bluebird, JavaScript, JS, Promise.map, Promises, TypeScript

Connect with me ...

LinkedIn
XING
Twitter
Medium