lambda-api icon indicating copy to clipboard operation
lambda-api copied to clipboard

Async operation before send

Open eXist-FraGGer opened this issue 4 years ago • 6 comments

Hello everyone!

I need to save some important data every time when the response is already available.

I have tried 2 approaches:

  1. api.finally - does not allow us to use an async callback.
api.finally(async (req, res) => {
  await saveAudit(req, res)
  res.cors()
  console.log('done')
})
  1. Tried to override the method res.send in a middleware - it looks like the send method can not include any async operations.
function auditHandler (handler) {
  return (req, res, next) => {
    res.sendRes= res.send
    res.send = function (body) {
      const data = {}
      saveAudit(data)
      .finally(() => {
        console.log('finally', resData)
        res.sendRes.call(this, body)
      })
    }
    next()
  }
}

As a result - the API does not work, but it works if we do not wait for an asynchronous operation.

image

eXist-FraGGer avatar Aug 04 '20 14:08 eXist-FraGGer

Interesting topic, I was thinking for something like middleware but then before the send

My use-case was that I would like to create a function for cache headers depending of the status code For that you need access to the response details, like statusCode, current headers etc

MarioVerbelen avatar Aug 04 '20 15:08 MarioVerbelen

I have checked the source code and see why it does not work

The api.finally supports async callback image

But the method res.send does not wait for the async method _callback

image

eXist-FraGGer avatar Aug 04 '20 15:08 eXist-FraGGer

It works if we do the next changes:

image 2. image

eXist-FraGGer avatar Aug 04 '20 15:08 eXist-FraGGer

There is a way how to do some async operations in finally.

  1. Use callback from lambda function
module.exports.handler = (event, context, callback) => {
  api.run(event, context, callback)
}
  1. Use async operation in api.finally - !!! important - do not use async / await
api.finally((req, res) => {
  saveAudit().finally(() => { console.log('done') })
})
  1. Why it works: The third argument, callback, is a function that you can call in non-async handlers to send a response. The callback function takes two arguments: an Error and a response. When you call it, Lambda waits for the event loop to be empty and then returns the response or error to the invoker.

https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html

eXist-FraGGer avatar Aug 06 '20 04:08 eXist-FraGGer

api.finally((req, res) ... works great

I see that in the handler you can also use then on run

return api.run(event, context)
    .then(res => {
        console.log(res);
        do stuff...;
        return res;
    })

MarioVerbelen avatar Aug 06 '20 07:08 MarioVerbelen

I too wish that finally could take an async function. I put the MySQL pool (from, ahem, jeremydaly/serverless-mysql) into a request field, and finally gets passed the request object, so it would be the better way to clean up afterward.

For now, instead, I keep the pool in a global variable and assign it to the request but clean up in the handler function:

const api = createAPI({logger: {access: true}});
api.use((req, res, next) => {
  req.mysql = prepareMySQL();
  next();
});

const func: APIGatewayProxyHandlerV2WithJWTAuthorizer = async
                      (event:APIGatewayProxyEventV2WithJWTAuthorizer, context:Context):
                      Promise<APIGatewayProxyStructuredResultV2> => {
  const result = await api.run(event, context);
  await cleanUpMySQL();
  return result;
}

export default func;

ammulder avatar May 06 '22 23:05 ammulder