I'm creating an API that check counter value, increment by one and after that creates new documents in document's collection. For that purpose I'm using runTransaction()
.
And I'm running into problem. Transactions works as expected, checks counter value and after that increment it by one. But after that, when I try to set new document, I get error (empty object) and I can't do that. I think there can be bad my used logic, so I need your advice where I am wrong.
const db = admin.firestore()
const couterRef = db.collection('invoices').doc('invoices_doc')
let counterValue = 0
// check last invoice counter number and increament by 1
return db.runTransaction((transaction) => {
return transaction
.get(couterRef)
.then((counter) => {
counterValue = counter.data().counter 1
// Update counter
return transaction.update(couterRef, { counter: counterValue })
})
.catch((error) => response.status(200).send({ status: 'TRANSACTION ERROR', error }))
})
.then(() => {
// Create new invoice document
couterRef.collection('invoices').doc(counterValue).set({
series: 'SS',
series_nr: counterValue
})
.then(() => response.status(200).send({ status: 'OK' }))
.catch((error) => response.status(200).send({ status: 'DOCUMENT SET ERROR', error }))
})
.catch((error) => {
response.status(200).send({ status: 'RUN TRANSACTION ERROR', error })
})
CodePudding user response:
I've not tested your code but the problem most probably comes from the fact that you modify the application state inside of your transaction functions, which is something you must avoid. There is a specific section about this problem in the documentation.
You need to pass the new value of counterValue
out of your transaction function, as follows:
const db = admin.firestore();
const couterRef = db.collection('invoices').doc('invoices_doc');
// let counterValue = 0 Remove this line
return db
.runTransaction((transaction) => {
return transaction.get(couterRef).then((counter) => {
const counterValue = counter.data().counter 1;
// See the changes below !!
transaction.update(couterRef, { counter: counterValue }); // Actually, this in not an asynchronous operation
return counterValue; // We pass the new value of counterValue out of the transaction function
});
})
.then((counterValue) => {
// Create new invoice document
couterRef.collection('invoices').doc(counterValue.toString(10)).set({
series: 'SS', // you missed a ,
series_nr: counterValue,
});
})
.then(() => response.status(200).send({ status: 'OK' }))
.catch((error) => {
response.status(200).send({ status: 'RUN TRANSACTION ERROR', error });
});
Also, in your HTTPS Cloud Function, don't send the response back to the client from WITHIN the Transaction: this is also an application state modification and shall not be done from within the transaction.
Similarly, don't include catch
blocks in the then
blocks: add a catch
block only once at the end of the promise chain. If you want to deal with different error types in this unique catch
block, just throw errors with different error messages and decide, in the catch
block, what to do depending on the message. Alternatively you can create some subclasses of the Error class.
Having said all of that, since your transaction only impacts one document and only increments a counter, you could very well use the FieldValue.increment()
method, which is atomic. See this Firebase blog post for more details.