Working with promises

Promises play an important role in the ArcGIS API for JavaScript. The better you understand promises, the more equipped you'll be to write cleaner code when working with asynchronous operations in your applications.

What is a promise?

On the most basic level, a promise is a representation of a future value returned from an asynchronous task. When the task executes, the promise allows other processes to run simultaneously, which gives users a better experience. In essence, a promise is a value that "promises" to be returned whenever the process completes. This is particularly useful when making multiple network requests where timing and download speeds can be unpredictable.

A promise is always in one of three states: pending, resolved, or rejected. When a promise resolves, it can resolve to a value or another promise as defined in a callback function. When a promise is rejected, it should be handled in an errback function.

Understanding .then()

Promises are commonly used with the .then() method. This is a powerful method that allows you to define the callback and errback functions. The first parameter is always the callback and the second is the errback.

someAsyncFunction().then(callback, errback);

The callback is invoked once the promise resolves and the errback is called if the promise is rejected.

someAsyncFunction().then(function(resolvedVal){
  // This is called when the promise resolves
  console.log(resolvedVal);  // Logs the value the promise resolves to
}, function(error){
  // This function is called when the promise is rejected
  console.error(error);  // Logs the error message
});

Another method for handling errors with rejected promises is with the catch function. See Esri Error for more information.

button.on("click", function() {
  esriRequest(url, options).then(function(response) {
    // do something useful
  }).catch(function(error){
    console.log("informative error message: ", error);
  });
});

Example: GeometryService.project()

In this example, we'll use the GeometryService to project several point geometries to a new spatial reference. In the documentation for GeometryService.project(), we see that project() returns a promise that resolves to an array of projected geometries.

require([
  "esri/tasks/GeometryService",
  "esri/tasks/support/ProjectParameters", ...
  ], function(GeometryService, ProjectParameters, ... ) {

    // Create a new instance of GeometryService
    var gs = new GeometryService( "http://sampleserver6.arcgisonline.com/arcgis/rest/services/Utilities/Geometry/GeometryServer" );
    // Set up the projection parameters
    var params = new ProjectParameters({
      geometries: [points],
      outSR: outSR,
      transformation = transformation
    });

    // Run the project function
    gs.project(params).then(function(projectedGeoms){
      // The promise resolves to the value stored in projectedGeoms
      console.log("projected points: ", projectedGeoms);
      // Do something with the projected geometries inside the .then() function
    }, function(error){
      // Print error if promise is rejected
      console.error(error);
    });
});

Chaining promises

One of the advantages to using promises is leveraging .then() to chain multiple promises together. This can clean up your code nicely by removing nested callback after nested callback, ultimately making your code easier to read. When you chain more than one promise together, the value of each process defined by .then() is dependent on the resolution of the previous promise. This allows you to sequence blocks of code without having to nest multiple callbacks within each other. It is important to note that each .then() that provides a value to another .then() must do so using the return command.

Example: geometryEngineAsync.geodesicBuffer()

We'll demonstrate chaining by continuing the previous projection example and using multiple .then() functions to perform some basic analysis on our projected points. For a live look at a similar example of chaining promises, see the Chaining Promises sample.


  /***************************************
  * See previous example for preceding code
  ****************************************/

  // Creates instance of GraphicsLayer to store buffer graphics
  var bufferLayer = new GraphicsLayer();

  // Project the points and chain the promise to other functions
  gs.project(params)
    .then(bufferPoints)             // When project() resolves, the points are sent to bufferPoints()
    .then(addGraphicsToBufferLayer) // When bufferPoints() resolves, the buffers are sent to addGraphicsToBufferLayer()
    .then(calculateAreas)           // When addGraphicsToBufferLayer() resolves, the buffer geometries are sent to calculateAreas()
    .then(sumArea)
    .catch(function(error){ // When calculateAreas() resolves, the areas are sent to sumArea()
      console.error('One of the promises in the chain was rejected! Message: ', error);
    });

  // Note how each function returns the value used as input for the next function in the chain

  // Buffers each point in the array by 1000 feet
  function bufferPoints(points) {
    return geometryEngine.geodesicBuffer(points, 1000, 'feet');
  }

  // Creates a new graphic for each buffer and adds it to the bufferLayer
  function addGraphicsToBufferLayer(buffers) {
    buffers.forEach(function(buffer) {
      bufferLayer.add(new Graphic(buffer));
    });
    return buffers;
  }

  // Calculates the area of each buffer and adds it to a new array
  function calculateAreas(buffers) {
    return buffers.map(function(buffer) {
      return geometryEngine.geodesicArea(buffer, 'square-feet');
    });
  }

  // Calculates the sum of all the areas in the array returned from the previous then()
  function sumArea(areas) {
    var total = 0;
    areas.forEach(function(area) {
      total += area;
    });
    console.log("Total buffered area = ", total, " square feet.");
  }

Additional resources

You can read more about promises in the MDN Promise documentation and the Dojo Promise documentation to get a more in-depth look at their structure and usage. The following are additional links to blogs that explain promises with other helpful examples: