# Promise.all() Is Not a Transaction: What Happens When One Promise Fails?

Imagine we need to refund $50 to a group of customers.

We have their customer IDs:

```javascript

const customerIds = ["C101", "C102", "C103", "C104"];
```

For each customer, we need to update their account and add the refund amount to their balance.

A straightforward implementation might process these customers one by one:

```javascript

for (const customerId of customerIds) {
  await refundCustomer(customerId, 50);
}
```

This works, but it can be slow because each database operation waits for the previous one to finish.

So we think:

> These refunds are independent. Why not run them concurrently?

We change the code to:

```javascript

await Promise.all(
  customerIds.map(customerId => refundCustomer(customerId, 50))
);
```

Now all refund operations are started concurrently instead of waiting for one another.

This can significantly reduce the total time when the operations are independent.

But there is a problem.

What exactly happens if one of those operations fails?

Suppose we are processing:

> C101 → Refund $50 → SUCCESS
> 
> C102 → Refund $50 → SUCCESS
> 
> C103 → Refund $50 → ❌ FAILED
> 
> C104 → Refund $50 → Still Running

Promise.all() rejects as soon as one of the promises rejects.

But the important question is:

> What happened to the other refund operations?

Did they automatically roll back?

Did Promise.all() undo the successful refunds?

No.

And this is where the difference between Promise.all() and a database transaction becomes important.

This gives us a very clean transition into the main topic:

> `Promise.all()` **coordinates promises. It does not provide transactional atomicity.**

* * *

### What Actually Happens?

When `Promise.all()` rejects because the refund for `C103` failed, it only tells us that the combined promise has failed. It does not undo the work that has already been completed by the other promises.

For example:

> C101 → Refund $50 → SUCCESS
> 
> C102 → Refund $50 → SUCCESS
> 
> C103 → Refund $50 → FAILED
> 
> C104 → Refund $50 → still running

At this point:

```javascript

try {
  await Promise.all(
    customerIds.map(customerId => refundCustomer(customerId, 50))
  );
} catch (error) {
  console.log("Refund process failed");
}
```

The `catch` block will execute because the promise for `C103` rejected.

But the `catch` block does not mean:

> C101 → rollback
> 
> C102 → rollback
> 
> C103 → rollback
> 
> C104 → stop

Instead, `C101` and `C102` may already have completed successfully, while `C104` may still be running (C104's refund operation does not automatically stop because C103's promise failed).

So the final state could be:

> C101 → refunded
> 
> C102 → refunded
> 
> C103 → not refunded
> 
> C104 → refunded

The application knows that `Promise.all()` failed, but `Promise.all()` does not provide a mechanism to return the system to the state it was in before the operation started.

This is the key difference between `Promise.all()` and a database transaction.

* * *

### What Would a Database Transaction Do?

If these updates were part of a single database transaction, the database could provide atomicity:

> BEGIN TRANSACTION
> 
> Refund C101
> 
> Refund C102
> 
> Refund C103 ← fails
> 
> ROLLBACK

The database can undo the changes made by `C101` and `C102` because those changes belong to the same database transaction and have not yet been committed.

With `Promise.all()`, there is no such transaction boundary:

> Promise.all()
> 
> ├── C101 → database operation
> 
> ├── C102 → database operation
> 
> ├── C103 → database operation ❌
> 
> └── C104 → database operation

`Promise.all()` only coordinates the promises. It does not control the database transactions behind them.

And this leads naturally to the next question:

> Why doesn't `Promise.all()` stop the other promises when one of them fails?

* * *

### Why Doesn't Promise.all() Stop the Other Promises?

To understand this, we need to distinguish between two different things: **promise rejection** and **cancellation**.

When one promise passed to `Promise.all()` rejects, `Promise.all()` itself immediately rejects. It stops waiting for the remaining promises to finish.

But it does not cancel those promises.

For example:

```javascript
const refunds = customerIds.map(
  customerId => refundCustomer(customerId, 50)
);

await Promise.all(refunds);
```

Suppose `C103` fails first:

> C101 → processing...
> 
> C102 → processing...
> 
> C103 → ❌ FAILED
> 
> C104 → processing...
> 
> ↓ Promise.all()
> 
> ↓ REJECTED

At this point, `Promise.all()` has finished with a rejected state. But `C101`, `C102`, and `C104` don't automatically stop.

If `C104` was already executing a database query, that query can continue executing even though `Promise.all()` has already rejected.

This is because a Promise represents the **result of an asynchronous operation**. Rejecting the Promise does not automatically provide a mechanism for stopping whatever work produced that Promise.

So these are two different events:

> Promise.all() rejects ≠ Underlying operations are cancelled

This distinction is easy to miss.

For our refund example, the application might execute:

> C101 → refund succeeds
> 
> C102 → refund succeeds
> 
> C103 → refund fails
> 
> C104 → refund succeeds

`Promise.all()` correctly tells us:

> At least one operation failed.

But it does not tell the database:

> Undo everything that has already happened.

And it certainly does not guarantee that operations which are still running will stop.

* * *

### But Can We Cancel Them?

Sometimes yes, but cancellation has to be explicitly supported by the underlying operation.

For example, some APIs such as `fetch()` can be cancelled using `AbortController`. But even then, cancellation is not the same thing as rollback.

If a database operation has already committed:

> Refund C101
> 
> ↓
> 
> COMMIT
> 
> ↓
> 
> Promise C103 fails

cancelling another operation cannot magically undo the committed refund for C101.

This brings us to an important distinction:

> **Cancellation tries to stop work that is still happening. Rollback reverses work that has already happened.**

`Promise.all()` provides neither automatically.

And that is why using `Promise.all()` for multiple operations with side effects requires more thought than simply replacing a `for...of` loop with concurrent execution.

### Conclusion

`Promise.all()` is useful when multiple independent asynchronous operations can run concurrently. But it only coordinates their promises; it does not make those operations atomic, automatically cancel them, or roll back side effects when one operation fails.

When concurrent operations modify state, we need to think beyond whether `Promise.all()` resolves or rejects. We need to consider what happens when some operations succeed, others fail, and some are still running.

> `Promise.all()` **gives you concurrency and a combined result. It does not give you atomicity.**
