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.