admin管理员组

文章数量:1336289

I have existing admin api code that I've simplifile down to 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: ${resolutionmitted ? 'mitted' : 'NOT-COMMITTED'}`);
    if (resolutionmitted) {
      // 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"}'

The console output is:

Item: {"test":"abc123"} at -KgTpp3FzgbLUrMNofNZ
Item ref: 
Value: {"test":"abc123"}
Txn resolution: mitted
Process: {"test":"abc123"}

I've been trying to move my code and this example to a cloud function. I realize that .on('child_added', f) and .onWrite(f) treat existing data differently but I can't get the transaction code to work correctly. The parameter passed to my transaction function is always null.

As 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: ${resolutionmitted ? 'mitted' : 'NOT-COMMITTED'}`);
    if (resolutionmitted) {
      // 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, the transaction never removes the item. Log output:

2017-03-30T10:51:19.387565284Z D receiveAndRemove: Function execution started
2017-03-30T10:51:19.395Z I receiveAndRemove: Item: {"test":"abc123"} at -KgTpp3FzgbLUrMNofNZ
2017-03-30T10:51:19.395Z I receiveAndRemove: Item ref: 
2017-03-30T10:51:19.396Z I receiveAndRemove: Value: null
2017-03-30T10:51:19.396Z I receiveAndRemove: Txn resolution: NOT-COMMITTED
2017-03-30T10:51:19.418446269Z D receiveAndRemove: Function execution took 32 ms, finished with status: 'ok'

Of course, the cloud function fails to remove the item and because the transaction didn't mit the remove, also doesn't process the item. I expect both to happen and I expect this code to work even when the node server version is running. The items should always be processed exactly once no matter how how many instances are running in the cloud and/or my server.

Is there some subtle difference in cloud functions I am missing? Is there something I'm doing with transactions incorrectly or that doesn't work with cloud functions?

Full source: .git

I have existing admin api code that I've simplifile down to 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.mitted ? 'mitted' : 'NOT-COMMITTED'}`);
    if (resolution.mitted) {
      // 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"}'

The console output is:

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

I've been trying to move my code and this example to a cloud function. I realize that .on('child_added', f) and .onWrite(f) treat existing data differently but I can't get the transaction code to work correctly. The parameter passed to my transaction function is always null.

As 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.mitted ? 'mitted' : 'NOT-COMMITTED'}`);
    if (resolution.mitted) {
      // 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, the transaction never removes the item. Log output:

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

Of course, the cloud function fails to remove the item and because the transaction didn't mit the remove, also doesn't process the item. I expect both to happen and I expect this code to work even when the node server version is running. The items should always be processed exactly once no matter how how many instances are running in the cloud and/or my server.

Is there some subtle difference in cloud functions I am missing? Is there something I'm doing with transactions incorrectly or that doesn't work with cloud functions?

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

Share edited Mar 30, 2017 at 13:18 Frank van Puffelen 600k85 gold badges889 silver badges859 bronze badges asked Mar 30, 2017 at 11:36 MikeMike 2,5791 gold badge28 silver badges32 bronze badges 3
  • Try returning the promise returned from the transaction by adding a return in front of itemRef.transaction. The Cloud Function will not stick around for long in your code sample since you aren't returning the promise, which tells the Cloud Function to wait until it is resolved. Not sure it will solve your problem, but it might. – jwngr Commented Mar 30, 2017 at 21:12
  • I will try that but the log shows the both the transaction callback runs and the then also runs – Mike Commented Mar 31, 2017 at 21:35
  • Returning the promise did not change the behaviour, the log looks exactly the same. The transaction callback gets null. – Mike Commented Apr 2, 2017 at 2:47
Add a ment  | 

2 Answers 2

Reset to default 7

The problem here is that in the scenario when the transaction value is null, you return undefined, which cancels the transaction. You actually need to handle the case when the value is null since Firebase may pass that value. The reason for this dive a bit into how Firebase transactions work.

In the first example, you have a local listener on the node you are doing the transaction on. This means you have the exact value for that node stored in the local cache. In the second example, you have the value for the node, but there is no actual listener for that node locally. The value es from Cloud Functions itself and is not stored locally. Thus, when you do the transaction, Firebase will try right away with a "guess" of the value, which is always null to start. The transaction will retry once it hears from the server that the value is not null, and the server will tell it what the new value is. Then, the transaction will retry. However, because you don't handle the null case and simply return undefined, the transaction is cancelled.

I don't think you really need a transaction for what you are trying to do though. You can get the value of item in both your code samples without doing a transaction. For example, here is how you can update your Cloud Functions 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();
});

From my understanding is that, cloud function is stateless therefore, there is no local cache. It always remove the instance if the method is no longer used after a certain period of time. That's why it always returns null.

本文标签: