Course contentsShow
← Node.js
Lesson 1430 of 1,98013. Error Handling and Resilient SystemsFree lesson

Asynchronous Transaction Rollbacks

You will orchestrate multi-step asynchronous processes to ensure that failures trigger compensating actions or reversions.

In this lesson, you will learn how to orchestrate multi-step asynchronous processes so that when one part of a sequence fails, the system automatically triggers compensating actions to revert previous successful operations.

When executing a business process that spans multiple asynchronous calls—such as charging a user's credit card, updating an inventory database, and sending a confirmation email—a failure in the final step leaves your system in a "partial" or inconsistent state. If the email fails, the customer has been charged, but they don't know it, and your inventory records are now off-sync.

The Compensating Action Pattern

To handle this, we use the Saga pattern approach: keep a stack of "undo" operations. If any step fails, we iterate through the stack in reverse order to undo the side effects of the previous, successful steps.

async function processOrder(order) {
  const undoStack = [];

  try {
    // Step 1: Charge Card
    const charge = await paymentGateway.charge(order);
    undoStack.push(() => paymentGateway.refund(charge.id));

    // Step 2: Update Inventory
    await inventory.reserve(order.items);
    undoStack.push(() => inventory.release(order.items));

    // Step 3: Send Email (The failure point)
    await mailer.send(order.receipt);
    
  } catch (err) {
    console.error("Transaction failed, rolling back...", err);
    // Execute rollbacks in reverse order
    for (const undo of undoStack.reverse()) {
      try {
        await undo();
      } catch (rollbackErr) {
        // Log critical failure: manual intervention required
        console.error("Rollback failed!", rollbackErr);
      }
    }
    throw err; // Propagate the original error
  }
}

Why This Matters

By wrapping your await calls in a try-catch block and maintaining an undoStack, you move away from "hope-based" architecture. Instead of leaving your system in an indeterminate state after an error occurs, you treat the entire multi-step process as a single atomic transaction that cleans up after itself. If an undo operation itself fails, you’ve reached a point where manual intervention is required, which is a significant improvement over silent data corruption or ghost charges.

Key Takeaway: Always pair every state-changing asynchronous action with a registered compensating operation to ensure that system consistency is maintained even when processes fail mid-stream.