serverless-express: Async handler doesn't work on node 8.10

I have reduced my project to that snippet which generates an error using the new 8.10:

import awsServerlessExpress from 'aws-serverless-express'
import express from 'express'

export default async (event, context, callback) => {
  const app = express()

  app.get('/', function(req, res){
    res.send('hello world')
  })

  const server = awsServerlessExpress.createServer(app)

  return awsServerlessExpress.proxy(server, event, context)
}

Babel compilation targets node 8.10 to be sure it doesn’t convert async/await to native js code.

Cloudwatch reports:

START RequestId: 3cb224ae-3810-11e8-ba0f-437e442fcf6a Version: $LATEST
Unable to stringify response body as json: Converting circular structure to JSON: TypeError

END RequestId: 3cb224ae-3810-11e8-ba0f-437e442fcf6a
REPORT RequestId: 3cb224ae-3810-11e8-ba0f-437e442fcf6a	Duration: 30027.56 ms	Billed Duration: 30000 ms 	Memory Size: 512 MB	Max Memory Used: 31 MB	

Removing the async in the handler fix the problem. But my whole project does some await in the handler which requires the handler to be async.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 13
  • Comments: 37

Commits related to this issue

Most upvoted comments

PR for this here https://github.com/awslabs/aws-serverless-express/pull/173. Note that I plan on improving the interface in a future breaking change (aws-serverless-express 4.0.0), but for now this is a backwards compatible change with the following interface:

exports.handler = async (event, context) => awsServerlessExpress.proxy(server, event, context, 'PROMISE').promise

module.exports = awsServerlessExpress.proxy(server, event, context, 'PROMISE').promise should work

I’ve tried what @brettstack said and it works (I’m using serverless-offline so I use process.env.IS_OFFLINE to pass correct handler to aws-serverless-express):

import awsServerlessExpress from 'aws-serverless-express';
import express from 'express';
import configureApp from './configureApp';

export default async (event, context) => {
  const app = express();

  await configureApp(app);

  app.get('/', function(req, res){
    res.send('hello world');
  });

  const server = awsServerlessExpress.createServer(app);

  return new Promise((resolve, reject) => {
    awsServerlessExpress.proxy(server, event, {
      ...context,
      succeed: process.env.IS_OFFLINE ? context.succeed : resolve,
    });
  });
}

I have it working in a local branch. Going to go with a non-breaking change and we’ll improve the overall API of this project in the future in a major version bump. I first want to add some additional integration tests before I merge in support. SAM has been taking priority for me lately, but I do want to get this in. For now I suggest patching in support https://github.com/awslabs/aws-serverless-express/issues/134#issuecomment-379417431

I’m now supporting this project again. V4 RCs are available http://npmjs.com/package/@vendia/serverless-express

Released in v3.3.3

Thanks. We’re working towards v4 where this will be the default.

With @brettstack no longer at AWS it seems development here has stalled. Will plans for V4 be resurrected?

Hey, thanks for providing the solution here !

Having this: https://github.com/awslabs/aws-serverless-express/issues/134#issuecomment-495026574

In the Readme or a sample would help big time for future users.

So, what is the relevant solution ? 3.3.6 tried everything from this discussion and still can’t get async handler working (

Oh great! Yeah I’m aware of the .promise() in the AWS SDK already. Sorry to bother you I didn’t took the time to check the code. Thanks!

@j0k3r @Bnaya

Sorry, I goofed the guidance in that comment (now updated). Tack a .promise at the end of that line and you should be good to go.

Here’s the test which covers this https://github.com/awslabs/aws-serverless-express/blob/master/__tests__/integration.js#L196-L200

And here’s the implementation https://github.com/awslabs/aws-serverless-express/blob/master/src/index.js#L210-L230

You can see I’m returning { promise: new Promise... instead of returning new Promise... directly. This decision was made for future extensibility.

I’ll give a try tomorrow. Thanks @josephktcheung to confirm that it worked.

Regarding moving stuff outside the handler, we usually do sth like that:

import awsServerlessExpress from 'aws-serverless-express';
import express from 'express';
import configureApp from './configureApp';

let server

export default async (event, context) => {
  if (server) {
    return awsServerlessExpress.proxy(server, event, context)
  }

  const app = express();

  await configureApp(app);

  app.get('/', function(req, res){
    res.send('hello world');
  });

  server = awsServerlessExpress.createServer(app)

  return awsServerlessExpress.proxy(server, event, context)
}

Otherwise I don’t know how can you move await configureApp(app); outside the handler since it’s an async call.

Okay so the problem is that Node.js 8.10 now allows you to resolve using Promises instead of the old callback and even older context.succeed (which this library uses). We did have a path forward for upgrading from context.succeed to callback by setting context.callbackWaitsForEmptyEventLoop = true (see #32) however, the new Promise pattern ignores that.

For now, you can’t use Node.js 8.10 with an async handler (or return a promise). I’ll update the library to work with the latest updates soon. Based on the examples you’ve provided so far, it’s recommended to perform these setup tasks outside of the handler for best performance (that way these things happen only on cold start, and not on every invocation)

I switched to what you did (and upgraded to 3.3.5):

-    return new Promise((resolve) => {
-      awsServerlessExpress.proxy(server, event, {
-        ...context,
-        succeed: resolve,
-      })
-    })
+    return awsServerlessExpress.proxy(server, event, context, 'PROMISE')

But now the APIGW returns 502 all the time. No error in log 😕