In firebase, why transactions do not work in cloud functions, as in admin admins?

I have an existing api admin code which I simplify for this for testing purposes (this works):

admin.database().ref('/dropbox').on('child_added', function (childSnap) { let item, itemRef = childSnap.ref; console.log(`Item: ${JSON.stringify(childSnap.val())} at ${childSnap.key}`); console.log(`Item ref: ${itemRef.toString()}`); itemRef.transaction(function (value) { console.log(`Value: ${JSON.stringify(value)}`); if (value) { item = value; return null; } }).then(function (resolution) { console.log(`Txn resolution: ${resolution.committed ? 'committed' : 'NOT-COMMITTED'}`); if (resolution.committed) { // process item console.log(`Process: ${JSON.stringify(item)}`); } else { // assume that item must have been removed by someone else } }).catch(function (err) { console.log(`Txn error: ${JSON.stringify(err, null, 2)}`); }); }); 

When I run:

firebase database: push / dropbox <<'{"test": "abc123"}'

Console output:

 Item: {"test":"abc123"} at -KgTpp3FzgbLUrMNofNZ Item ref: https://cloud-function-txn-test.firebaseio.com/dropbox/-KgTpp3FzgbLUrMNofNZ Value: {"test":"abc123"} Txn resolution: committed Process: {"test":"abc123"} 

I am trying to move my code and this example to a cloud function. I understand that .on ('child_added', f) and .onWrite (f) process existing data differently, but I cannot get the transaction code to work correctly. The parameter passed to my transaction function is always zero.

Like a cloud function (this does not work):

 exports.receiveAndRemove = functions.database.ref('/dropbox/{entryId}').onWrite(function (event) { if (!event.data.exists()) { return; } let item, itemRef = event.data.adminRef; console.log(`Item: ${JSON.stringify(event.data.val())} at ${event.data.key}`); console.log(`Item ref: ${itemRef.toString()}`); itemRef.transaction(function (value) { console.log(`Value: ${JSON.stringify(value)}`); if (value) { item = value; return null; } }).then(function (resolution) { console.log(`Txn resolution: ${resolution.committed ? 'committed' : 'NOT-COMMITTED'}`); if (resolution.committed) { // process item console.log(`Process: ${JSON.stringify(item)}`); } else { // bad to assume here that item must have been removed by someone else } }).catch(function (err) { console.log(`Txn error: ${JSON.stringify(err, null, 2)}`); }); }); 

For some reason, a transaction never deletes an item. Log output:

 2017-03-30T10:51:19.387565284ZD receiveAndRemove: Function execution started 2017-03-30T10:51:19.395ZI receiveAndRemove: Item: {"test":"abc123"} at -KgTpp3FzgbLUrMNofNZ 2017-03-30T10:51:19.395ZI receiveAndRemove: Item ref: https://cloud-function-txn-test.firebaseio.com/dropbox/-KgTpp3FzgbLUrMNofNZ 2017-03-30T10:51:19.396ZI receiveAndRemove: Value: null 2017-03-30T10:51:19.396ZI receiveAndRemove: Txn resolution: NOT-COMMITTED 2017-03-30T10:51:19.418446269ZD receiveAndRemove: Function execution took 32 ms, finished with status: 'ok' 

Of course, the cloud function does not delete the element, and since the transaction does not perform the deletion, it also does not process the element. I expect this to happen, and I expect this code to work even if the node server version is running. Elements should always be processed exactly once no matter how many instances are running in the cloud and / or on my server.

Is there any subtle difference in the cloud features that I am missing? Is there something that I am doing with transactions incorrectly or that does not work with cloud functions?

Full source: https://github.com/mscalora/cloud-function-txn-test.git

+5
source share
2 answers

The problem is that when the transaction value is null , you return undefined , which cancels the transaction. You really need to handle the case where the value is null , since Firebase can pass this value. The reason for this is a little dive into Firebase transaction work.

In the first example, you have a local listener on the node with which the transaction is being performed. This means that you have the exact value for the node stored in the local cache. In the second example, you have a value for node, but for this node there is no actual listener locally. The value comes from the cloud functions themselves and is not stored locally. Thus, when you make a transaction, Firebase will immediately try to "guess" the value, which will always be null to run. The transaction will retry after it hears from the server that the value is not null , and the server tells it what the new value is. Then the transaction will be retried. However, since you are not handling the null case and just return undefined , the transaction is canceled.

I don’t think you really need a transaction for what you are trying to do. You can get the item value in both code samples without a transaction. For example, here is how you can update the cloud function example:

 exports.receiveAndRemove = functions.database.ref('/dropbox/{entryId}').onWrite(function (event) { if (!event.data.exists()) { return; } let item = event.data.val(); let itemRef = event.data.adminRef; console.log(`Item: ${JSON.stringify(item)} at ${event.data.key}`); console.log(`Item ref: ${itemRef.toString()}`); return itemRef.remove(); }); 
+6
source

From my understanding, the cloud function has no state, so there is no local cache. It always deletes the instance if this method is no longer used after a certain period of time. Therefore, it always returns null.

0
source

Source: https://habr.com/ru/post/1266117/


All Articles