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.