Summary
Using async / await within the lambda handler function throws an error when not passing a callback into the midi constructor function. AWS documentation specifies that we don't need to pass a callback when using async / await
https://aws.amazon.com/blogs/compute/node-js-8-10-runtime-now-available-in-aws-lambda/
Example Code
index.js
const middy = require('middy')
const handler = middy(async (event, context) => {
return {foo: 'bar'}
})
handler('asd', 'asd')
Running this throws an error:
yarn start
yarn run v1.7.0
$ node index.js
(node:25665) UnhandledPromiseRejectionWarning: TypeError: callback is not a function
at terminate (/Users/mellisdesigns/Documents/Boilerplates/lambda-node-boilerplate/node_modules/middy/src/middy.js:151:16)
at runNext (/Users/mellisdesigns/Documents/Boilerplates/lambda-node-boilerplate/node_modules/middy/src/middy.js:125:14)
at runErrorMiddlewares (/Users/mellisdesigns/Documents/Boilerplates/lambda-node-boilerplate/node_modules/middy/src/middy.js:129:3)
at errorHandler (/Users/mellisdesigns/Documents/Boilerplates/lambda-node-boilerplate/node_modules/middy/src/middy.js:159:14)
at onHandlerError (/Users/mellisdesigns/Documents/Boilerplates/lambda-node-boilerplate/node_modules/middy/src/middy.js:167:16)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:182:7)
at Function.Module.runMain (internal/modules/cjs/loader.js:697:11)
at startup (internal/bootstrap/node.js:201:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:516:3)
(node:25665) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:25665) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
✨ Done in 0.15s.
Runtime Version
System:
OS: macOS High Sierra 10.13.4
CPU: x64 Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
Binaries:
Node: 9.11.1 - ~/.nvm/versions/node/v9.11.1/bin/node
Yarn: 1.7.0 - /usr/local/bin/yarn
npm: 6.1.0-next.0 - ~/.nvm/versions/node/v9.11.1/bin/npm
Further investigation show that when aws invokes the lambda it always passes three arguments. The only issue with that, is when you want to run your code locally without the callback parameter ...
Either documentation should be more clear, or code should be handled which I have created a pr for.
Thanks @mellisdesigns for this. This should probably be considered as a bug in middy.
I am not sure what could be the best way to handle this, because middy adds its own layer to support async await mainly for 2 reasons:
I'll mark this one as a bug for now and I hope I'll have a bit more time soon to investigate possible solutions.
In any case, we should definitely improve the documentation to make clear what happens when not passing the callback and encourage people to always do that!
Hi there, I just took a look at the PR #199 as I now only work with async handler and saw that the issue was not yet resolved. I'm going to start working on a possible fix and will keep you updated. To give you an overview, if we want to cover all the possible cases of an invoke, we should handle the following calls:
function invokeHandlerWithContext (event, context) {
// return context.fail(new Error('failure'))
context.succeed('success')
}
function invokeHandlerWithCallback (event, context, callback) {
// return callback(new Error('failure'))
callback(null, 'success')
}
async function invokeAsyncHandler (event, context) {
// throw new Error('failure')
return 'success'
}
Thanks @kevinrambaud, I definitely overlooked the first and the last case you mentioned, so i wouldn't be surprised if async/await doesn't work well in these situations
It should also be noted that when wrapping an async handler, middy will not return a promise. This will cause problems if you want to test your function, for example.
I'm not sure what the implication would be in AWS Lambda itself.
async handler(event, context) {
return 'Hello';
}
handler(event, context); // -> Promise
middy(handler)(event, context) // -> undefined
Is this on the cards to be fixed? If not is anyone aware of a workaround? :-)
@Kjir I confirmed my understanding of the issue similarly. In order to test my lambda's I did something like this for now:
const promisify = async (handler, event, context) => {
return new Promise((resolve, reject) => {
handler(event, context, (err, response) => {
if (err) {
reject(err)
} else {
resolve(response)
}
})
})
}
it("responds with a 422 if dsafsdafasf is not specified", async () => {
const resp = await promisify(create, { body: JSON.stringify({}) })
expect(resp.statusCode).toEqual(422)
})
not wild about it, but it works. It would be great to have await wait for all middlewares to flush and return a promise. So that a standard await handler(event, context) invocation works as expected.
@dpickett thank you! That worked a charm. Not ideal but it will work for me until this issue is resolved.
You might make use of util.promisify (Requires node >= v8.0.0)
@Kjir even better then! Thanks very much.
I didn't have luck with util.promisify, but I did with @dpickett code example. thanks!
Closed by #314 (only for v1.x)
I am also getting "TypeError: callback is not a function" when attempting to test my handlers using Middy. Is there a simpler solution as opposed to the one described above? Thanks.
@kevinswarner any updates for the issue you've mentioned?
@ItayKatzCC No. I ended up writing my own middleware for our needs, not specifically because of this, but we also had some different requirements.
Most helpful comment
@Kjir I confirmed my understanding of the issue similarly. In order to test my lambda's I did something like this for now:
not wild about it, but it works. It would be great to have
awaitwait for all middlewares to flush and return a promise. So that a standardawait handler(event, context)invocation works as expected.