Async Initialisation of a Lambda Handler

AWSLambdaDaily EmailFriday Function Fun

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 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.

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.

Sidenote on Provisioned Concurrency: Unfortunately this approach does not ensure that your async init code finishes as part of the provisioning warmup phase. When I tested this on a function with PC enabled, I found that the init function was paused by Lambda and only then unpaused whenever the first function invocation was made.

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

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.

— Paul.

Originally published .Last updated .

Other articles you might enjoy:

Free Email Course

How to transition your team to a serverless-first mindset

In this 5-day email course, you’ll learn:

  • Lesson 1: Why serverless is inevitable
  • Lesson 2: How to identify a candidate project for your first serverless application
  • Lesson 3: How to compose the building blocks that AWS provides
  • Lesson 4: Common mistakes to avoid when building your first serverless application
  • Lesson 5: How to break ground on your first serverless project