How async Lambda handlers work in Node 8.10

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.