The checkout request failed. The inventory reservation still completed.
If your error handler assumes that failure means nothing happened, the retry can be the most dangerous line in the feature.
Consider a hypothetical implementation that starts an inventory reservation and a notification together, then awaits Promise.all. Notification fails. The page shows an error. The reservation is still in flight.
Reproduce the order without a timer
This small JavaScript example lets us finish one operation manually after the combined promise has already rejected:
const events = [];
let finishReservation;
const reservation = new Promise(resolve => {
finishReservation = () => {
events.push("stock reserved");
resolve();
};
});
try {
await Promise.all([
reservation,
Promise.reject(new Error("notification unavailable"))
]);
} catch {
events.push("request failed");
}
finishReservation();
await reservation;
console.log(events);
// ["request failed", "stock reserved"]
The resulting event order is the point: a side effect can finish after the caller enters its failure path. The example models that side effect with an array; it does not connect to a real store.
Promise.all rejects when an input rejects. That does not cancel the other operations or undo completed writes. Promise.allSettled can collect every outcome, but it does not provide rollback either.
Review the state left behind
For this checkout, I would ask the reviewer to fill in a small failure table:
| Observation | Question before offering retry |
|---|---|
| Reservation succeeded, notification failed | Can notification be retried separately? |
| Reservation is still pending | Where can its final state be checked? |
| The response was lost | Can the existing operation be identified? |
| Both operations succeeded | Which record establishes the completed order? |
“Catch and show a toast” leaves these questions unanswered. The user can see a failure while the server holds a reservation.
Pick the boundary that must be consistent
For this example, inventory and order state belong in a server workflow with an explicit consistency strategy. Sending a notification can follow a committed order, with its own delivery status. The exact design depends on which services own the data.
Putting the calls in sequence can prevent a later step from starting too early. It still does not undo a first step when the second fails. A transaction is useful only within the boundary it actually covers; several unrelated HTTP APIs do not become one transaction because they share an await.
If retry is supported, define what identifies the same operation. Check the destination's contract before assuming that repeating a write is safe. A random new identifier on every retry does not identify the original attempt.
Ask a harder question of generated code
“Does the happy path work?” is easy to demonstrate. Try this review request instead:
Fail the second operation after the first has started. Show the final server state, then show what a retry does.
Run the same exercise on code written by a person or by a coding assistant. The source of the code does not change the failure semantics.
Which side effect in your app could finish after the UI has already told the user the operation failed?
Top comments (0)