Course contentsShow
← Node.js
Lesson 209 of 1,9803. Asynchronous Patterns and Flow ControlFree lesson

The Async/Await Reentrancy Trap

You will identify and resolve race conditions caused by non-atomic async/await function calls.

In this lesson, you will learn how to prevent "reentrancy traps," where multiple asynchronous executions of the same function overlap and corrupt your application's state.

The Reentrancy Trap

When you mark a function as async, it immediately returns a Promise and frees the event loop while waiting for an await statement. If a user clicks a button or triggers an event multiple times, that same function can be called again while the first execution is still "in-flight." If these functions share a common state—like writing to a file or updating a database—they will interfere with each other.

Consider this fragile implementation of a file saver:

let isSaving = false;

async function saveSettings(data) {
  if (isSaving) return; // Basic guard
  isSaving = true;

  // The trap: The event loop pauses here
  await writeFile('settings.json', JSON.stringify(data));
  
  isSaving = false;
}

If saveSettings is called twice rapidly, the second call might finish before the first, or both might try to modify the same resource simultaneously. Because the function "re-enters" its logic while the first await is still pending, your state becomes non-atomic.

Resolving the Trap

To prevent this, you must ensure that your asynchronous logic is either atomic or queued. Instead of just checking a flag, you can use a "Promise lock." By storing the current operation as a Promise, subsequent calls can simply wait for the previous one to complete before starting their own work.

let activeSave = Promise.resolve();

async function safeSave(data) {
  // Chain the new save onto the current promise
  activeSave = activeSave.then(async () => {
    await writeFile('settings.json', JSON.stringify(data));
  });
  
  return activeSave;
}

By chaining the operations, you force the executions to line up in a strictly sequential queue. The function effectively becomes "re-entrant safe" because it respects the timeline of the previous operations rather than just checking a boolean flag that ignores incoming requests.

Key Takeaway: Never rely on simple boolean flags to manage async state; instead, chain your operations using a Promise variable to force atomic execution and prevent state corruption during overlap.