今天來研究怎麼在 Cloud Firestore 進行 Transactions 吧。先把相關的 API 列出來:
firebase.firestore.Firestore
runTransaction(updateFunction) returns Promise
updateFunction(non-null firebase.firestore.Transaction)
firebase.firestore.Transaction
set(documentRef, data, options) returns firebase.firestore.Transaction
get(documentRef) returns Promise containing non-null firebase.firestore.DocumentSnapshot
update(documentRef, ...var_args) returns firebase.firestore.Transaction
delete(documentRef) returns firebase.firestore.Transaction
在這裡以要為一個城市的人口增加 10000 為例。首先,我們要透過 runTransaction()
方法執行一個 transactions。在這個方法的參數放的就是想要以 transation 執行的內容所構成的匿名函式,這個函式會得到一個 Transaction 物件,我們就可以透過這個物件提供的 set()
、get()
、update()
、delete()
去操作 Document Reference 物件,這些方法的參數和Document Reference 物件的同名方法相同,只是會插入 Document Reference 物件作為第一個餐數。
當我們發現這個城市不存在於資料庫時就會丟出例外,並且在 runTransaction()
回傳的 Promise 物件的 catch()
方法中進行處理;若是所有敘述都成功執行,就會跑到runTransaction()
回傳的 Promise 物件的 then()
方法中進行。
let cityRef = db.collection('cities').doc('taichung')
db.runTransaction((transaction) => {
return transaction.get(cityRef).then(citySnapshot => {
if (!citySnapshot.exists) {
throw 'Document does not exist!'
}
let increment = 10000
let population = citySnapshot.get('population') + increment
transaction.update(cityRef, { population: population })
})
}).then(function() {
console.log('Transaction successfully committed!')
}).catch(function(error) {
console.log('Transaction failed: ', error)
})
因為 transaction 可能會衝突存在導致執行超過一次,為了避免 concurrency 的問題,極度不建議在 transaction 中修改 App 的資訊,而是透過將想修改的資訊傳到 transaction 外部後載進行修改。
若是想要將 transaction 內的資訊傳到外部,可以善用 return
、throw
、Promise.resolve()
、Promise.reject()
2 等方法將資訊往外傳,如下例:
let cityRef = db.collection('cities').doc('taichung')
db.runTransaction((transaction) => {
return transaction.get(cityRef).then(citySnapshot => {
if (!citySnapshot.exists) {
throw 'Document does not exist!'
}
let increment = 10000
let newPopulation = citySnapshot.get('population') + increment
if (newPopulation <= 1000000) {
transaction.update(cityRef, { population: newPopulation })
return Promise.resolve('Population increased to ' + newPopulation)
} else {
return Promise.reject('Sorry! Population is too big.')
}
})
}).then(result => {
// Output: Transaction success: Population increased to 110000
console.log('Transaction success', result)
}).catch(error => {
// Output: Transaction failed: Sorry! Population is too big.
console.log('Transaction failed: ', error)
})