DEV Community

Cover image for Async Initialisation of a Lambda Handler
Paul Swail
Paul Swail

Posted on • Originally published at winterwindsoftware.com

Async Initialisation of a Lambda Handler

Each Friday, I will share a small tip with you on something Lambda/FaaS-related. Because Fridays are fun, and so are functions. 🥳

Today we'll cover how to perform some asynchronous initialisation outside of your Lambda handler in Node.js.

For example, you may need to fetch configuration data from SSM Parameter Store or S3 that the main body of your function depends upon.

There are a few points to consider here before we start coding:

  1. Our initialisation code should only be executed once — on the first "cold start" execution.
  2. Our initialisation data may not be loaded by the time the execution of the handler function body starts.
  3. JavaScript does not allow await calls to be defined at the root level of a module. They must happen inside a function marked as async.
  4. If our Lambda function has Provisioned Currency enabled, you want this initialisation to be performed during the background warming phase and not when the function is serving an actual request.
  5. If our initialisation code fails, it should be re-attempted on subsequent invocation as the first failure could be due to a transient issue.

Let's jump to the code:


const init = async () => {
  // Perform any async calls here to fetch config data.
  // We'll just dummy up a fake promise as a simulation.
  return new Promise((resolve, reject) => {
    console.log('fetching config data...');
    resolve({ myVar1: 'abc', myVar2: 'xyz' });
  });
};

const initPromise = init();

exports.handler = async (event) => {
  // Ensure init has completed before proceeding
  const functionConfig = await initPromise;
  // Start your main handler logic...
  console.log('functionConfig is set:', functionConfig);
};

The init function is responsible for asynchronously fetching an object containing all configuration data required for the function. Note that it is triggered as soon as the module is loaded and not inside the handler function. This ensures that the config is fetched as early as possible. It also should ensure that this initialisation processing will happen in the warming phase of a function with Provisioned Concurrency enabled.

The second key point here is that a promise returned from the init function is stored at the module scope and then awaited upon inside the handler. This ensures that your function can safely continue. Subsequent invocations will proceed immediately as they will be awaiting on an already resolved promise.

So far we've covered off requirements 1–4 from our list above. But what about #5?

What if an error occurs when loading the config data due to some transient issue and the init function rejects? That would mean that all subsequent executions will keep failing and you'd have a dead Lambda function container hanging around until it's eventually garbage collected.

Actually no! The Lambda runtime manages this case for you. If any errors occur in the initialisation code outside your handler, the function container is terminated and a new one is started up in a fresh state. If the transient issue has passed, your init function will resolve successfully. 😃

Thanks to Tow Kowalski, Jeremy Daly and particularly Michael Hart whose suggestions in this Twitter thread prompted this tip.

💌 If you enjoyed this article, you can sign up to my newsletter. I send emails every weekday where I share my guides and deep dives on building serverless solutions on AWS with hundreds of developers and architects.

Originally published at winterwindsoftware.com.

Top comments (0)