Middy: Using an async function with middy without callback throws error.

Created on 5 Jun 2018  Â·  16Comments  Â·  Source: middyjs/middy

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
bug

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:

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.

All 16 comments

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:

  • we want to support node 6.10 as well
  • this was done before node 8 was released in Lambda and there was no other option

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dschep picture dschep  Â·  6Comments

lmammino picture lmammino  Â·  3Comments

chrisandrews7 picture chrisandrews7  Â·  6Comments

vladgolubev picture vladgolubev  Â·  4Comments

lmammino picture lmammino  Â·  4Comments