Firebase: Transaction with async/await

10,616

Solution 1

IMPORTANT: As noted by a couple of the users, this solution doesn't use the transaction properly. It just gets the doc using a transaction, but the update runs outside of it.

Check alsky's answer. https://stackoverflow.com/a/52452831/683157


Take a look to the documentation, runTransaction must receive the updateFunction function as parameter. (https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore#runTransaction)

Try this

var docRef = admin.firestore().collection("docs").doc(docId);
let doc = await admin.firestore().runTransaction(t => t.get(docRef));

if (!doc.exists) {throw ("doc not found");}
var newLikes = doc.data().likes + 1;

await doc.ref.update({ likes: newLikes });

Solution 2

The above did not work for me and resulted in this error: "[Error: Every document read in a transaction must also be written.]".

The below code makes use of async/await and works fine.

try{
   await db.runTransaction(async transaction => {
       const doc = await transaction.get(ref);
       if(!doc.exists){
            throw "Document does not exist";
       }
       const newCount = doc.data().count + 1;
       transaction.update(ref, {
           count: newCount,
       });
  })
} catch(e){
   console.log('transaction failed', e);
}

Solution 3

If you look at the docs you see that the function passed to runTransaction is a function returning a promise (the result of transaction.get().then()). Since an async function is just a function returning a promise you might as well write db.runTransaction(async transaction => {})

You only need to return something from this function if you want to pass data out of the transaction. For example if you only perform updates you won't return anything. Also note that the update function returns the transaction itself so you can chain them:

try {
    await db.runTransaction(async transaction => {
      transaction
        .update(
          db.collection("col1").doc(id1),
          dataFor1
        )
        .update(
          db.collection("col2").doc(id2),
          dataFor2
        );
    });
  } catch (err) {
    throw new Error(`Failed transaction: ${err.message}`);
  }

Solution 4

In my case, the only way I could get to run my transaction was:

const firestore = admin.firestore();
const txRes = await firestore.runTransaction(async (tx) => {
    const docRef = await tx.get( firestore.collection('posts').doc( context.params.postId ) );
    if(!docRef.exists) {
        throw new Error('Error - onWrite: docRef does not exist');
    }
    const totalComments = docRef.data().comments + 1;
    return tx.update(docRef.ref, { comments: totalComments }, {});
});

I needed to add my 'collection().doc()' to tx.get directly and when calling tx.update, I needed to apply 'docRef.ref', without '.ref' was not working...

Share:
10,616
rendom
Author by

rendom

Updated on June 05, 2022

Comments

  • rendom
    rendom about 2 years

    I'm trying to use async/await with transaction. But getting error "Argument "updateFunction" is not a valid function."

    var docRef = admin.firestore().collection("docs").doc(docId);
    let transaction = admin.firestore().runTransaction();
    let doc = await transaction.get(docRef);
    
    if (!doc.exists) {throw ("doc not found");}
    var newLikes = doc.data().likes + 1;
    
    await transaction.update(docRef, { likes: newLikes });
    
  • rendom
    rendom over 6 years
    Where do we define transaction variable? It gives me error transaction is not defined now.
  • Nicolas Castellanos
    Nicolas Castellanos over 6 years
    @rendom Oops, I forgot that. Just use doc.ref instead
  • Jus10
    Jus10 almost 6 years
    What if we wanted to write to multiple references inside the transaction? transaction.update(sfDocRef1, data); transaction.update(sfDocRef2, data);
  • kuboon
    kuboon over 5 years
    This does nothing with transaction. You need to use t with update. see alsky's answer. stackoverflow.com/a/52452831/683157
  • Thijs Koerselman
    Thijs Koerselman almost 5 years
    @Jus10 Seems like the return value of update is the transaction itself, so you can chain them: await db.runTransaction(async transaction => { transaction .update( db.collection("guidelines").doc(guidelineId), updateData ) .update( db.collection("guideline_revisions").doc(revisionId), updateData ); });
  • Thijs Koerselman
    Thijs Koerselman almost 5 years
    @rendom please reconsider your accepted answer, since this one doesn't use transactions properly (as kuboon noted).
  • Dipen Bhikadya
    Dipen Bhikadya almost 5 years
    As noted by kuboon and ThijsKoerselman, this solution doesn't seem to be using the transaction properly. It gets the doc using a transaction, but the update runs outside the transaction. Answer needs to be corrected, or stackoverflow.com/a/52452831/683157 should be the accepted answer.
  • linus_hologram
    linus_hologram over 4 years
    Will this solution even work if the read fails? Shouldn't async/await normally be inside a try/catch block? Thanks in advance for your help :)
  • Ryan Saunders
    Ryan Saunders about 4 years
    If the read fails because of concurrent edits, it will automatically retry the transaction. But if the read fails due to a bad transaction, then yes, you'd probably want to catch the transaction failure. See: firebase.google.com/docs/firestore/manage-data/…