demo · v131

Database transactions with await using

The TC39 proposal's motivating use case wasn't any async resource — it was the database transaction. This version uses a real IndexedDB object store: success commits a two-leg transfer, while a thrown error aborts the transaction so the debit is rolled back.

scenario

commit (success)

Both writes queue successfully → [Symbol.asyncDispose] calls IDBTransaction.commit() and waits for complete.

scenario

throw (failure)

The debit write is queued, then the body throws → [Symbol.asyncDispose] calls abort() and the stored balances stay unchanged.

log

the pattern

function idbTransactionScope(db) {
  const tx = db.transaction("accounts", "readwrite");
  return {
    store: tx.objectStore("accounts"),
    committed: false,
    async [Symbol.asyncDispose]() {
      if (this.committed) tx.commit();
      else tx.abort();
      await transactionFinished(tx);
    }
  };
}

async function transfer(db, shouldFail) {
  await using tx = idbTransactionScope(db);
  tx.store.put({ id: "A", balance: 400 });
  if (shouldFail) throw new Error("credit leg rejected");
  tx.store.put({ id: "B", balance: 150 });
  tx.committed = true;
}

see also