How async Lambda handlers work in Node 8.10

By Rich

Thu Jun 14 2018

With the Node 8.10 runtime AWS added a new async handler syntax

async handler(event, context) {
  return result;
}

while continuing to support the old callback handler syntax.

handler(event, context, callback) {
  callback(null, result);
}

From the number of messages posted on the Serverless Forums a lot of developers seem to believe the async keyword is syntactic sugar that doesn’t do anything. In fact the new async syntax is functionally the equivalent of

handler(event, context) {
  return new Promise((resolve, reject) => resolve(result));
}

It’s important to understand that functions declared async are actually returning a Promise because it appears that Lambda is now checking the return value from your handler function to determine what it does next. When the handler returns a Promise then Lambda waits for the promise to resolve using it’s resolved value as the result for the Lambda.

If I was to speculate on the internal workings of Lambda I would think that it looks something like this.

const p = handler(event, context, callback);
if (p instanceof Promise) {
  p.then(result => callback(null, result)).catch(err => callback(err));
}

This has led to some common mistakes that would have worked if async hadn’t been used. Look at the following example

async handler(event, context, callback) {
  doSomething()
    .then(result => callback(null, result));
}

What’s going on here and why doesn’t this work?

Using async causes the handler() to return a Promise (promise #1) that resolves at the end of the function. As we step through the function we call doSomething() which returns a Promise (promise #2). Execution then proceeds to the next line of code which is the end of the function so promise #1 will now resolve with the value undefined.

What happened to promise #2 and the .then()? The .then() is only executed after promise #2 resolves. While technically it’s a race to see which resolves first in practice promise #1 will always resolve before promise #2 resulting in undefined being sent as the response from the Lambda.

Without the async keyword this would have worked because the handler function would have returned undefined instead of a Promise.

This also has an interesting side effect that you can now return a Promise from a handler function that hasn’t been delcared async which allows code like

handler(event, context) {
  return doSomething()
    .then(res1 => somethingElse(res1))
    .then(res2 => { message: `Value is ${res2}` });
}

Importantly it means you shouldn’t mix async and callback when using the Node 8.10 runtime.

Want to learn more about serverless applications and devops with AWS?

Sign up for our newsletter.