Promises are a great feature in JavaScript enabling you to avoid callback hell, especially when you need to wait on a response from multiple API requests that are asynchronous. Promises make it easy to elegantly solve the problem of waiting for one async request to finish before performing another async request, or waiting for multiple async requests to finish before moving forward.

The problem

In our code pattern “Create a cognitive news search app,” we use the Watson Node SDK. These APIs rely heavily on callbacks instead of promises, where the callback gets passed error as the first argument and response as the second. This makes it necessary to check if error is not undefined or null before we can use the response.

This type of code can result in a lot of nested callbacks and a huge callback function body. Since both success and error states need to be handled, the code is hard to read and understand:

const DiscoveryV1 = require('watson-developer-cloud/discovery/v1');

// Initialize the Discovery service with credentials from IBM Cloud
const discovery = new DiscoveryV1({
  username: '',
  password: '',
  version_date: DiscoveryV1.VERSION_DATE_2017_04_27
});

// Get all of the environments
discovery.getEnvironments({}, (error, response) => {
  if (error) {
    console.error(error);
  } else {

    // Find the environemnt id for News collection
    const news_environment_id = response.environments
      .find(env => env.read_only == true).environment_id;

    // Get the New collection ID using the environment
    discovery.getCollections({ environment_id: news_environment_id }, (error, response) => {
      if (error) {
        console.error(error);
      } else {
        const news_collection_id = response.collections[0].collection_id;

        // Now we can query discovery news collection
        discovery.query({
          environment_id: news_environment_id,
          collection_id: news_collection_id
          query: 'my_query'
        }, (error, response) => {
          if (error) {
            console.error(error);
          } else {
            // By the time we get the response we have 6 levels of nesting
            console.log(response);
          }
        });
      }
    });
  })
});

Promises and Bluebird to the rescue

To fix this anti-pattern, we can use Javascript Promises along with a library called Bluebird to provide a higher level abstraction around promises. The library is great! It lets us wait on multiple promises before firing a callback. The following does the same as the previous code, but wraps some of the functions used in the Discovery API to return promises using the promisify factory method:

const DiscoveryV1 = require('watson-developer-cloud/discovery/v1');

// Initialize the Discovery service
const discovery = new DiscoveryV1({
  username: '',
  password: '',
  version_date: DiscoveryV1.VERSION_DATE_2017_04_27
});

// Promisify the API using Bluebird's promisify factory function
discovery.getEnvironments = Promise.promisify(discovery.getEnvironments);
discovery.getCollections = Promise.promisify(discovery.getCollections);
discovery.query = Promise.promisify(discovery.query);

let environmentId;
discovery.getEnvironments({})
.then(response => { // Response for environment gets passed in the then block
  environmentId = response.environments
    .find(env => env.read_only == true).environment_id;

  // Returning a promise inside the then block will cause
  // the next `then` block to fire when the discovery.getCollection
  // async request finishes and passes the result of getCollection
  // as the first argument to the callback in the next `then` function
  return discovery.getCollections({ environment_id: environmentId });
})

// This then block will get called when the getCollection async request
// finished and passed the result in the response
.then(response => {
  collectionId = response.collections[0].collection_id;

  // Now we can run a query against our discovery service
  return discovery.query({
      environment_id: news_environment_id,
      collection_id: news_collection_id
      query: 'my_query'
    })
})

// The response of the discovery query gets passed to this
// then block
.then(response => console.log(response));

// Any errors that occur anywhere in this flow gets caught by this catch
// block and can be logged and handled independently from the success cases above
.catch(error => console.error(error));

Less nesting and straight-line data flow

As seen in the “promisified” code, we now have only one level of nesting, versus the six levels that we had previously. Additionally, the success and error workflows are now separated, making our code easy to understand and debug.

Another benefit of using promises results from returning a promise inside of a then block. The code waits for that promise to resolve before invoking the next then block with the response containing the resolved value of the returned promise:

.then(response => {
  //...

  // Returning a promise results in the next chained
  // then callback function to be called when this promise resolve
  return discovery.query({
      environment_id: news_environment_id,
      collection_id: news_collection_id
      query: 'my_query'
    })
})

// The response of the discovery query gets passed to this
// then block
.then(response => console.log(response));

Promises kept

The bottom line? Use promises rather than callbacks wherever possible. And if the API you’re using doesn’t support them, use a library like Bluebird, which can help you wrap the API to return a promise.

I hope this helps simplify your use of the Discovery API and shows you how Javascript Promises can be used to simplify the workflow in your code. I’d love to get your feedback on your own experince using promises.