How to make “Fire-and-forget” HTTP request from Node.js without waiting for response.

Lambda functions at zeit/now v2 platform have default execution timeout 10s.
Another limitation important to consider:
– the process gets killed right after the response was sent.
When you have a job to be done in the lambda, not required immediately to prepare HTTP response (e.g. writing log to the database)
– it’s a good idea to send a response to the client ASAP and do rest of the work “in the background”.
But there are no built-in ways to do such things in zeit/now v2 lambdas.

The only way everybody talks but nobody tried is to extract such task into separate lambda and call it via another HTTP request.
It sounds easy, but let’s take a closer look:

  • λ1 was asked for a response. If it says anything – the game is over, no job can be done anymore.
  • so let λ1 ask λ2 to do rest of the job
  • and then respond to the original request

Ok?
How to ask λ2 to do something?

  1. make a request → get 200OK → respond
    get 200OK? λ2 is lambda too. It can’t say anything until the job is finished or it will be killed.
    So λ1 still have to wait for all the job is done before responding.

  2. make a request → “let it go” → respond
    How to let it go? Don’t pass any callback, don’t wait for any promise.
    But this way response to the original request will be sent too soon and λ1 will be killed.
    λ2 will have no chance to receive a request, parse args and perform any action.

  3. Make sure λ2 got the request, but don’t wait for a response:

//delegate.js
const https = require('https');
module.exports = async (host, message) => {
  message = JSON.stringify(message);
  var options = {
    hostname: host,
    method: 'POST',
    path: '/lambda2',
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(message),
    },
  };
  await new Promise((resolve, reject) => {
    let req = https.request(options);
    req.on('error', (e) => {
      console.error(`problem with request: ${e.message}`);
      reject(e);
    });
    req.write(message);
    req.end(() => {
      console.log("NOW it's not λ1's problem anymore.");
      resolve();
    });
  });
};
//lambda1.js
const delegate = require('./delegate');
module.exports = async (req, res) => {
  let host = req.headers['x-now-deployment-url'];
  await delegate(host,'Hey, you, λ2! Did you hear an order?');
  res.end('Yes, my Master! Will be done!')
};
//lambda2.js
module.exports = async (req, res) => {
  console.log("Oh, so much to be done! Let's start immediately!");
  await new Promise((resolve) => {
    setTimeout(() => {
      console.log('Nothing was done successfully!');
      send(res, 200);
      resolve();
    }, 2000);
  });
};

Hope this piece of code will save someone’s work day!