Getting Error: `every Document Read In A Transaction Must Also Be Written` In Firebase
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 taskers
const 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 yet
if (!transdoc.exists) {
// check if there is only one manpower required for the task
// mark the status of the transaction 'ongoing' if so
const 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 taskId
const approved = await transdoc.get('approved')
// check if the current approved list from
// the transactions collection hasn't
// reached the manpower quota yet
if (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 quota
if (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 data
export function runTransaction(updateFunction) {
return db.runTransaction(transaction => {
const docRefsRequested = [];
let didSetRequestedDocs = false;
function setEachRequestedDoc() {
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 });
return Promise.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);
},
};
return updateFunction(transactionWrapper).then(resolveValue => {
setEachRequestedDoc();
return Promise.resolve(resolveValue);
});
});
}
Post a Comment for "Getting Error: `every Document Read In A Transaction Must Also Be Written` In Firebase"