Parallel Promises in Node (or how to wrangle multiple Promises)

Promises offer a much easier pattern for handling asynchronous requests. Compared to the callback pattern Promises allow for much cleaner and maintainable code.

A common pattern encountered when working with APIs is the need to do several requests at once, and return all the data in a single response. For example:

Imagine an app where you've requested users who have Liked a photo on Instagram. This can be accomplished with a GET request to /media/[media-id]/likes. The response will be an shallow array of users:

{
  "data": [{
    "username": "jack",
    "first_name": "Jack",
    "last_name": "Dorsey",
    "type": "user",
    "id": "66"
  },
  {
    "username": "sammyjack",
    "first_name": "Sammy",
    "last_name": "Jack",
    "type": "user",
    "id": "29648"
  }]
} 

'shallow' in the sense that the we're receiving some basic info about the users who have Liked the photo. Now, let's say we want to display each users bio photo. To do this we need to get the details for each user. This is done with a GET request to /users/[user-id].

You can imagine how this could get out of hand quickly. If a dozen users have Liked the photo we need to send a dozen requests to the /users/[user_id] endpoint, and wrangle a dozen responses.

This is where Promises, and specifically Promises' ability to handle parallel requests comes into play.

Pretend the response from GET /media/[media_id]/likes is an array of length 10. That is, 10 users have Liked the photo.

Now we need to get the details for each of these 10 users by sending a request to /users/[user_id] for all 10 users. We'll do this using .map and creating an array (users) containing the Promise returned from each of the requests.

// The array of user IDs
var user_ids = [1,2,3,4,5,6,7,8,9,10];

// Use .map to issue the request of each user and push the Promise 
// to a users variable.
var users = user_ids.map(function(id) {
  return new Promise(function(resolve, reject) {
    return unirest.get('https://api.instagram.com/v1/users/' + id)
                  .end(function(data){
                  resolve(data);
    });
  });
});

In this example we're using the Unirest HTTP request library. In the callback of the GET request we call resolve(data).

Then use Promise.all() to wait for all 10 requests to complete so we can send our final response to the client. In this example I'm pretending we're in Node, so we would use res.send() to send our final response.

// Use Promise.all to wait for all requests to finish
// and send the response to the client.
Promise.all(users).then(function(result) {
  var content = result.map(function(user) {
    return user.body;    
  });

  res.send(content);
});

The response sent to the client will be an array containing the details for all 10 users.

Comments