I am trying to rewrite my firebase-functions using transactions. The question I now come upon is, should I return a Promise of Transcations? For example, I want to use a function which has a transcation parameter. But this function should not instantly perform the transaction, but instead return the Promise of it.
Here is my current function:
export const dbUserDocsOnCreated = functions
.region(constants.region)
.firestore
.document("users/{id}")
.onCreate((snapshot, context) => doOnCreatedUserDocs(db, stripe, snapshot, context));
export async function doOnCreatedUserDocs(
db: FirebaseFirestore.Firestore,
stripe: Stripe,
snapshot: functions.firestore.QueryDocumentSnapshot,
context: functions.EventContext
) {
try {
const transactionPromise = db.runTransaction(async (t) => {
const snapshotData: FirebaseFirestore.DocumentData = (await t.get(snapshot.ref)).data() as FirebaseFirestore.DocumentData
// IF onCreate() has already been called once.
if (!isDataNullOrEmpty(snapshotData.eventID)) {
return Promise.all([null])
}
const createdUserNr: string = await getIdTransactionally(t, db, constants.collectionUsers)
const createdStripeUser: Stripe.Response<Stripe.Customer> = await createStripeUser(stripe, snapshotData, firebaseUserEmail, currentUserNr, snapshot.id, idempotencyKey)
const updateUserNrPromise: Promise<FirebaseFirestore.Transaction> = incrementIdTransactionally(t, db, constants.collectionUsers)
const updateUserDocsPromise: Promise<FirebaseFirestore.Transaction> = updateUserDocsTransactionally(t, snapshot.ref, createdUserNr, idempotencyKey)
return Promise.all([
updateUserDocsPromise,
updateUserNrPromise,
])
})
return Promise.all([
transactionPromise
])
} catch (err) {
// CATCHING
}
}
export async function incrementIdTransactionally(
transaction: FirebaseFirestore.Transaction,
db: FirebaseFirestore.Firestore,
doc: string
): Promise<FirebaseFirestore.Transaction> {
const incrementor: FirebaseFirestore.FieldValue = FirebaseFirestore.FieldValue.increment(1);
const collectionReference: FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData> = db.collection(constants.collectionAutoIds).doc(doc)
return transaction.update(collectionReference, { id: incrementor })
}
async function updateUserDocsTransactionally(
transaction: FirebaseFirestore.Transaction,
ref: FirebaseFirestore.DocumentReference,
generatedUserNr: String,
eventId: string,
): Promise<FirebaseFirestore.Transaction> {
return transaction.set(ref, {
userNr: generatedUserNr,
eventId: eventId,
optionalAddress: FirebaseFirestore.FieldValue.delete()
}, { merge: true })
}
function createStripeUser(
stripe: Stripe,
data: FirebaseFirestore.DocumentData,
eMail: string,
usernr: string,
uid: string,
idempotencKey: string,
): Promise<Stripe.Response<Stripe.Customer>> {
return stripe.customers.create({
name: data.fullName,
email: eMail,
description: "Created by Firebase Cloud-Functions",
metadata: { uuid: uid, userNr: usernr },
preferred_locales: ["de-DE"],
}, { idempotencyKey: idempotencKey })
}
After reviewing the previous thread and the SDK reference, it looks like the set()
, update()
, and delete()
operations of transactions do not return promises, but they just return the same Transaction
instance to be used in transaction chaining:
Returns Transaction: This Transaction instance. Used for chaining method calls.
This is not the same for the get()
method, which does return a Promise<DocumentSnapshot>
. I tested a simple transaction broken up into two functions, and found no apparent issues:
function fireTransaction(){
return fireDB.runTransaction((tr) => {
const myDoc = fireDB.collection("users").doc("testUser1");
return tr.get(myDoc).then(tDoc => {
setUserAgeTR(tr, tDoc.ref)
})
})
.then(res => console.log("Completed"))
.catch(err => console.log("Failed Transaction: ", err))
}
function setUserAgeTR(tr, docRef){
return tr.update(docRef, {userAge: 9999}) //Requires DocumentReference, can be obtained from 'ref'
}
Since you are using TypeScript, the return type for your helper functions should be Transaction
, according to the SDK doc.