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.