Andy Bedinger

Using try...catch with the AWS SDK for JavaScript

1/4/2022

A common use case for a Node.js Lambda function is to make an asynchronous call to some method in the AWS SDK for JavaScript. As an example, suppose you're using an asychronous handler in Lambda, and want to copy an object to an S3 bucket. You want to wait for the operation to finish, so you decide to use await. You want your Lambda to handle errors, so you additionally decide to use a try...catch block, and wind up with this:

const AWS = require('aws-sdk');
// Set the region 
AWS.config.update({region: 'us-east-1'});

// Create S3 service object
const s3 = new AWS.S3({apiVersion: '2006-03-01'});

exports.handler = async function(event) {
  var params = {
    Bucket: "my-destination-bucket", 
    CopySource: "/my-source-bucket/my-file.txt", 
    Key: "my-file-copy.txt"
  };

  try {
    await s3.copyObject(params);
  } catch (err) {
    throw(err);
  }
}

Let's assume you've already given the Lambda function the necessary permissions for its execution role. When you invoke the Lambda, it runs without error and really, really fast. Too fast. You check the destination bucket, and there's no file in it. What's happening?

What await Does

While it may seem like it, await isn't a magic word you can put in front of anything in JavaScript to make an asychronous event blocking. It does make an asynchronous function pause execution - if you use it correctly.

await takes a promise. If it's not given a promise, it treats the expression it was given (whatever follows await) as a resolved promise. Once you give await a resolved promise, it's not going to wait anymore.

That sounds like what's happening! The Lambda function is flying right through to the end and not waiting for the S3 copy operation to complete. Why?

What s3.copyObject(params) Returns

await takes a promise. Is s3.copyObject returning a promise? No. The copyObject method returns an AWS.Request object.

How to Fix This

If we could just get s3.copyObject to return a promise, we'd be all set. The good news is, we can! We can use the AWS.Request.promise method. We just have to change one line in the Lambda function above:

await s3.copyObject(params).promise();

Now, await is getting what it expects: a promise. The Lambda function's execution will block while the s3.copyObject is being performed. And, since the catch block of a try...catch statement handles any exceptions thrown by whatever happens in the try block, we can handle any errors that might come from the s3.copyObject attempt.

How does that work? Note the definition for the copyObject method: copyObject(params = {}, callback) ⇒ AWS.Request. It takes a callback function, which is of this format: function(err, data) { ... }. We're not passing a callback function, though. Quoting from the AWS documentation on the AWS.Request.promise method:

Using promises, a single callback isn't responsible for detecting errors. Instead, the correct callback is called based on the success or failure of a request.

So that's how. If the s3.copyObject attempt is successful, execution continues in the try block. If the attempt is not successful, then the catch block is executed.

Conclusion

The AWS.Request.promise() method comes in quite handy when working with asynchronous Node.js Lambda functions. Most example code I see for Node.js Lambda functions involves .then() and .catch(). If you prefer using await to keep things simple, like me, then AWS.Request.promise() is your friend.