Node.js中Firestore事务多文档读取实现Token转账原子操作
How to Read Both User Balances in a Firestore Transaction
Great question! The key here is that you need to perform both document reads before executing any writes in your transaction. Since each t.get() returns a Promise, you can use Promise.all() to fetch both user documents in parallel—this is efficient and ensures all required reads are completed first, which is a critical rule for Firestore transactions.
Here's the adjusted code that properly reads both balances before updating:
const db = admin.firestore(); const user1Ref = db.collection('users').doc(user1Id); const user2Ref = db.collection('users').doc(user2Id); db.runTransaction(t => { // Fetch both user documents simultaneously using Promise.all return Promise.all([t.get(user1Ref), t.get(user2Ref)]) .then(([user1Snap, user2Snap]) => { // Extract balances from both snapshots const user1Balance = user1Snap.data().balance; const user2Balance = user2Snap.data().balance; // Optional (but highly recommended): Add balance validation if (user1Balance < 100) { throw new Error('User 1 has insufficient tokens to complete the transfer'); } // Execute the atomic updates t.update(user1Ref, { balance: user1Balance - 100 }); t.update(user2Ref, { balance: user2Balance + 100 }); return `Transferred 100 tokens from ${user1Id} to ${user2Id}`; }); }) .then(result => { console.log('Transaction success', result); }) .catch(error => { console.log('Transaction failed', error); });
Important Details:
- Promise.all(): This method waits for both
t.get()promises to resolve before moving to the update step, ensuring all reads are done before any writes (Firestore enforces this to maintain transaction integrity). - Array Destructuring: The
([user1Snap, user2Snap])syntax lets you easily unpack the two snapshots from the resolved promise array. - Validation Check: Adding a balance check prevents invalid transfers and triggers a transaction failure if the condition isn't met. Firestore will automatically retry transactions in case of conflicts, but this validation stops unnecessary retries for impossible scenarios.
- Error Handling: The
.catch()block ensures you can handle failures like network issues, insufficient funds, or exhausted retries gracefully.
This approach guarantees atomicity: either both balance updates succeed, or neither does, keeping your database data consistent.
内容的提问来源于stack exchange,提问作者Dori
相关产品推荐
相关产品推荐

