The two types of Lambda handler functions

AWS supports the LTS (even numbered) versions of Node.js, and as newer versions come up, new features are available for Lambda functions. The traditional callback-style handler functions are being gradually replaced by async functions. In this post, I'll give an overview on both types of lambda functions.

In the last post, I wrote about the arguments of the lambda handler function, which are event and context objects. But there is a third argument available, and this is the callback function, which is called when an error needs to be thrown or a value should be returned.

1. Node versions supported

AWS announced the end of support for Node.js version 6 a few weeks ago, and they now encourage users to upgrade to at least version 8. Since the referred AWS article was written, AWS has introduced support for version 10 as well.

But before that, when AWS supported Node.js version 6, it wasn’t possible to use async functions, because they are only available from version 7.7. Instead, the handler functions used a third argument, which was a callback function.

This structure of the handler function still exists, so currently we have two formats to write a handler function.

2. async handler functions

Since you can’t create a new lambda function with Node v6 as its runtime, the most straightforward way is to write it as an async function.

async lambda functions can do three things: return a result, throw an error or return a Promise if an asynchronous task needs to be performed.

2.1. Return a response

To return a response, writing a lambda handler is no different from creating a regular, well-behaved async function.

In the following examples, the event object will have a property called num, which is a number, and the lambda function will multiply this number by 2. It happens that we have to multiply numbers by 2. I do it almost every day (although not exclusively in the code).

The handler in the module index (that is, the name of the file containing the code is index.js) can look like this:

exports.handler = async (event) => {
  const numberToMultiply = event.num
  try {
    const result = await multiplyByTwo(numberToMultiply)
    // we can do something with 'result' here, like add 1 to it
    return result + 1
  } catch (e) {
    throw new Error(e)
  }
}

function multiplyByTwo(num) {
  return new Promise(function(res, rej) {
    if (isNaN(num)) {
      rej('Please enter a number')
    }
    res(num * 2)
  })
}

Here we don’t use the context argument, so it’s not necessary to include it in the function definition.

This is the traditional try...catch block, which is frequently used with the async/await syntax. Inside an async function, we can await multiplyByTwo, and then the result can be returned. If we want or need to, we can do something with result before returning a response.

2.2. Return a promise

With the sad path (in our case, when the type of the parameter is not a number) the promise is rejected, and we can throw an error.

The third option is to return the promise itself, which is very useful when the lambda function needs to interact with another AWS services, like S3 or DynamoDB. In our case, if we don’t want to do anything else but multiplying the number by 2, we can write something like this:

exports.handler = async (event) => {
  return multiplyByTwo(event.num)
}

Lambda will send either the response or the error to the invoker depending on the promise resolving or rejecting.

3. Handler with callback

Some tutorials haven’t been updated yet, and they still contain a callback as the third argument for the handler function. This is not a big issue (only a bit inconvenient), and it will perfectly work with either version 8 or version 10. On the other hand, if, for some weird and unexpected reasons, you need to use callbacks (which shouldn’t be the case), the code can look like this:

exports.handler = function(event, context, callback) {
  const numberToMultiply = event.num
  multiplyByTwo(numberToMultiply)
    .then(function(result) {
      callback(null, `${ numberToMultiply } multiplied by 2 is ${ result }`)
    })
    .catch(callback)
}

callback is a traditional error-first type callback function. Its first argument is the error, which is passed in inside the catch block.

When everything goes well (inside then), we want to pass in null as the first argument (there’s no error here), and the second argument will be the value we would return inside the async version.

This is how the handler with callback relates to the async type handler: The async function should return what the callback function is called with as the second argument.

Using async functions is easier and more elegant than callbacks, and I hope that I’m not alone with my bias. Either way, Lambda manages the invocation and throws the error if necessary.

4. Summary

Lambda handlers can come in two formats. One is the old but still supported callback, which is the third argument of the lambda function. It’s a classic error-first callback.

The second type is a newer async function, which is supported from Node version 8.x runtime in AWS. This syntax is easier to write and understand.

Thanks for reading, and see you next time.