AWS Developer Tools Blog

Support for Promises in the SDK

Today’s release of the AWS SDK for JavaScript (v2.3.0) introduces support for promises when calling service operations. Promises provide an alternative to the use of a callback function to manage asynchronous flow. They allow treating asynchronous calls as a variable, simplifying error handling and providing greater control over handling results from asynchronous calls.

For more information about promises, check out out this entry on MDN!

This post describes how to use promises within the context of the SDK to handle completing asynchronous tasks.

Setting a Promise Library

By default, the AWS SDK for JavaScript will check for a globally defined Promise function. If found, it adds the promise() method on AWS.Request objects. Some environments, such as Internet Explorer or earlier versions of Node.js, don’t support promises natively. You can use the AWS.config.setPromisesDependency() method to supply a Promise constructor. After this method is called, promise() will exist on all AWS.Request objects. The following example shows how you can set a custom promise implementation for the AWS SDK to use.

Node.js Example

// Use bluebird implementation of Promise
AWS.config.setPromisesDependency(require('bluebird'));
// Use Q implementation of Promise
AWS.config.setPromisesDependency(require('Q').Promise);
// Use 'rsvp' implementation of Promise
AWS.config.setPromisesDependency(require('rsvp').Promise);
// Revert back to using native or globally available Promise
AWS.config.setPromisesDependency(null);

Browser Example

In the browser, a library that implements promises and provides a global Promise namespace, (bluebird) should be loaded before the AWS SDK is loaded. The AWS SDK will then find the namespace and use it internally. If the AWS SDK is loaded before the library, or the library uses a namespace other than Promise, then the namespace can be provided by calling AWS.config.setPromisesDependency():

// AWS SDK was loaded after bluebird, set promise dependency
AWS.config.setPromisesDependency(Promise);

Making Requests by Using Promises

Instead of using callbacks, the AWS.Request.promise() method provides a way to call a service operation and return a promise to manage asynchronous flow instead of callbacks. In node.js and the browser, an AWS.Request is returned when a service operation is called without a callback function. You could call send() on the request to make the service call. The promise() method immediately starts the service call and returns a promise.

The following examples provide a comparison between the use of a simplified callback method and promises to make a simple request.

Simplified Callback Method

In the following example, a callback method that accepts error and data objects is supplied. The logic to handle errors or successful requests is contained in the callback method.

var s3 = new AWS.S3({apiVersion: '2006-03-01', region: 'us-west-2'});
var params = {
  Bucket: 'bucket',
  Key: 'example1.txt',
  Body: 'Uploaded text using the simplified callback method!'
};
s3.putObject(params, function(err, data) {
  if (err) {
    console.log(err);
  } else {
    console.log('Success');
  }
});

Promise-Based Method

In the following example, a promise is returned that is fulfilled with a data object, or rejected with an error object. Using promises, a single callback isn’t responsible for detecting errors. Instead, the correct callback will be called based on the success or failure of a request.

var s3 = new AWS.S3({apiVersion: '2006-03-01', region: 'us-west-2'});
var params = {
  Bucket: 'bucket',
  Key: 'example2.txt',
  Body: 'Uploaded text using the promise-based method!'
};
var putObjectPromise = s3.putObject(params).promise();
putObjectPromise.then(function(data) {
  console.log('Success');
}).catch(function(err) {
  console.log(err);
});

The following example demonstrates how to write a simple script that uploads a directory of files to an Amazon S3 bucket, and then sends an email after the upload is complete.

Promises Example

// Check if environment supports native promises
if (typeof Promise === 'undefined') {
  AWS.config.setPromisesDependency(require('bluebird'));
}

var s3 = new AWS.S3({apiVersion: '2006-03-01', region: 'us-west-2'});
var ses = new AWS.SES({apiVersion: '2010-12-01', region: 'us-west-2'});

// Take a list of objects containing file data and send an email
var sendEmail = function sendEmail(files) {
  var keys = files.map(function(file) {
    return file.key;
  });
  var body = keys.join('n') + 'nnobjects were successfully uploaded.';
  var params = {
    Source: 'from@email.com',
    Destination: {
      ToAddresses: ['to@email.com']
    },
    Message: {
      Subject: {
        Data: 'Batch PutObject job completed'
      },
      Body: {
        Text: {
          Data: body
        }
      }
    }
  };
  return ses.sendEmail(params).promise();
};

// Upload a list of files to an S3 bucket
var putBatch = function putBatch(bucket, files) {
  // Make all the putObject calls immediately
  // Will return rejected promise if any requests fail
  return Promise.all(files.map(function(file) {
    var params = {
      Bucket: bucket,
      Key: file.key,
      Body: file.stream
    };
    return s3.putObject(params).promise();
  }));
};

// Create streams for files
var fileNames = fs.readdirSync('/path/to/dir/');
var files = fileNames.map(function(fileName) {
  return {
    key: fileName,
    stream: fs.createReadStream('/path/to/dir/' + fileName)
  };
});

//Upload directory of files to S3 bucket and send an email on success
putBatch('myBucket', files)
  .then(sendEmail.bind(null, files))
  .catch(console.error.bind(console));

You’ll notice we aren’t checking for errors inside our callback functions to determine the action to take. Instead, we can attach a single catch callback to our promise chain that will handle any errors thrown by our functions. Our code also reads as a series of steps to take (putBatch then sendEmail) instead of having to nest callbacks inside callbacks.

When a promise is fulfilled, the registered callback will have access to the data object as the first argument. We could have also provided a second callback that would be executed if the promise was rejected, but used a single catch statement instead. Rejected promises will pass an error object as the first argument to a callback.

Give It a Try!

We would love to hear what you think of this new feature. Give the AWS SDK for JavaScript v2.3.0 a try and leave your feedback in the comments or on GitHub!