Skip to content Skip to sidebar Skip to footer

Getting Error: `every Document Read In A Transaction Must Also Be Written` In Firebase

guys! i am developing an app similar to https://airtasker.com where users outsource tasks. the taskers would bid to the tasks, and wait for the user to approve their bids. these a

Solution 1:

to those who are having the same problem, here is my (hackish) solution:

for every condition,

add a document write (could be a set() update() or delete()) that corresponds to each of the document reads which in my code: the use of get()s.

and return a Promise

here's the updated code:

// a transaction is added if the user starts to approve offers// this function allows multiple taskersconst acceptOffer = async (taskerId, taskId, bidId, offer) => {
  let bulk
  try {
    const taskRef = db.collection('tasks').doc(taskId)
    const transRef = db.collection('transactions').doc(taskId)
    const bidRef = db.collection('bids').doc(bidId)

    const fees = solveFees(offer)

    bulk = await db
      .runTransaction(async t => {
        const transdoc = await t.get(transRef)
        const taskdoc = await t.get(taskRef)
        const manpower = await taskdoc.get('manpower')

        // check if a transaction exists with the given taskId// if it doesn't, then the task doesn't have// any approved bidders yetif (!transdoc.exists) {
          // check if there is only one manpower required for the task// mark the status of the transaction 'ongoing' if soconst status = manpower === 1
            ? 'ongoing' : 'pending'// add a transaction with the approved tasker
          t.set(transRef, {
            taskId,
            status, // pending, ongoing, completed
            approved: [
              { taskerId, ...fees }
            ]
          })

          // mark the bid 'accepted'
          t.update(bidRef, {
            accepted: true
          })

          // hackish (to prevent firestore transaction errors)
          t.update(taskRef, {})

          return Promise.resolve(true)
        } else { // if a transaction exists with the given taskIdconst approved = await transdoc.get('approved')

          // check if the current approved list from// the transactions collection hasn't// reached the manpower quota yetif (approved.length < manpower) {
            // push new approved bid of the tasker
            approved.push({ taskerId, ...fees })
            t.update(transRef, { approved })

            t.update(bidRef, { accepted: true }) // mark the bid 'accepted'
            t.update(taskRef, {}) // hackish// if, after pushing a new transaction,// the approved list reached the manpower quotaif (approved.length === manpower) {
              t.update(taskRef, { open: false }) // mark the task 'close'
              t.update(transRef, { status: 'ongoing' }) // mark the transaction 'ongoing'
              t.update(bidRef, {}) // hackish
            }
            return Promise.resolve(true)
          }
          return Promise.reject(new Error('Task closed!'))
        }
      })
  } catch (e) {
    swal('Oh, no!',
      'This task might already be closed',
      'error'
    )
    throw e
  }

  if (bulk) {
    swal('Offer accepted!', '', 'success')
  }
}

Solution 2:

I ran into the same issue. As long as google will not be able to sent validation errors with better errors than just that the client was not allowed to write the data (security rules). I prefer to handle it on client site. So I use transactions for example to validate that a referenced doc is still available when I write data. (for example I have write an order document that references to a customer and want be sure that the customer still exists.) So I have to read it but actually there is no need to write it.

I came up with something close to nrions solution but tried to have a more general approach for it so I wrote a wrapper for runTransaction. Of cause it is not the cleanest way to do it but maybe it is useful for others.

// Transaction neads to write all docs read be transaction.get().// To work around this we we call an update with {} for each document requested by transaction.get() before writing any dataexportfunctionrunTransaction(updateFunction) {
  return db.runTransaction(transaction => {
    const docRefsRequested = [];
    let didSetRequestedDocs = false;

    functionsetEachRequestedDoc() {
      if (didSetRequestedDocs) {
        return;
      }
      didSetRequestedDocs = true;
      docRefsRequested.forEach(({ exists, ref }) => {
        if (exists) {
          transaction.update(ref, {});
        } else {
          transaction.delete(ref);
        }
      });
    }

    const transactionWrapper = {
      get: function(documentRef) {
        return transaction.get(ref).then(snapshot => {
          const { exists } = snapshot;
          docRefsRequested.push({ ref, exists });
          returnPromise.resolve(snapshot);
        });
      },
      set: function(documentRef, data) {
        setEachRequestedDoc();
        return transaction.set(documentRef, data);
      },
      update: function(documentRef, data) {
        setEachRequestedDoc();
        return transaction.update(documentRef, data);
      },
      delete: function(documentRef) {
        setEachRequestedDoc();
        return transaction.delete(documentRef);
      },
    };

    returnupdateFunction(transactionWrapper).then(resolveValue => {
      setEachRequestedDoc();
      returnPromise.resolve(resolveValue);
    });
  });
}

Post a Comment for "Getting Error: `every Document Read In A Transaction Must Also Be Written` In Firebase"