【问题标题】:In firebase, why don't transactions work in cloud functions like they do in the admin api?在 firebase 中,为什么事务不能像在管理 api 中那样在云功能中工作?
【发布时间】:2017-08-24 07:32:35
【问题描述】:

我有现有的管理 api 代码,我已将其简化为用于测试目的(这可行):

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)}`);
  });

});

当我跑步时:

firebase 数据库:推送 /dropbox

控制台输出为:

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"}

我一直在尝试将我的代码和此示例移至云函数。我意识到 .on('child_added', f) 和 .onWrite(f) 以不同的方式处理现有数据,但我无法让事务代码正常工作。传递给我的事务函数的参数始终为空。

作为云功能(这不起作用):

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)}`);
  });

});

由于某种原因,交易永远不会删除该项目。日志输出:

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.com/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'

当然,云功能无法移除项目,并且由于事务没有提交移除,因此也不会处理该项目。我希望两者都会发生,并且即使节点服务器版本正在运行,我也希望此代码能够正常工作。无论在云和/或我的服务器中有多少实例正在运行,这些项目都应该只处理一次。

我缺少的云功能有什么细微差别吗?我在处理事务时是否存在错误或不适用于云功能?

完整来源:https://github.com/mscalora/cloud-function-txn-test.git

【问题讨论】:

  • 尝试通过在itemRef.transaction 前面添加return 来返回从事务返回的承诺。 Cloud Function 不会在您的代码示例中停留很长时间,因为您没有返回 promise,它告诉 Cloud Function 等待直到它被解决。不确定它会解决你的问题,但它可能会。
  • 我会尝试,但日志显示事务回调运行和 then 也运行
  • 返回承诺并没有改变行为,日志看起来完全一样。交易回调为空。

标签: javascript firebase transactions firebase-realtime-database google-cloud-functions


【解决方案1】:

据我了解,云功能是无状态的,因此没有本地缓存​​。如果在一段时间后不再使用该方法,它总是删除该实例。这就是为什么它总是返回 null。

【讨论】:

    【解决方案2】:

    这里的问题是,在事务值为null的场景下,你返回undefined,就取消了事务。您实际上需要处理值为 null 的情况,因为 Firebase 可能会传递该值。这次深入了解 Firebase 事务的工作原理的原因。

    在第一个示例中,您在进行事务处理的节点上有一个本地侦听器。这意味着您拥有存储在本地缓存中的该节点的确切值。在第二个示例中,您拥有节点的值,但本地没有该节点的实际侦听器。该值来自 Cloud Functions 本身,并不存储在本地。因此,当您进行交易时,Firebase 将立即尝试“猜测”该值,该值始终以 null 开头。一旦从服务器听到值不是null,事务将重试,服务器将告诉它新值是什么。然后,事务将重试。但是,由于您没有处理 null 的情况并简单地返回 undefined,因此交易被取消。

    我不认为你真的需要为你正在尝试做的事情进行交易。您可以在两个代码示例中获得 item 的值,而无需执行事务。例如,以下是更新 Cloud Functions 示例的方法:

    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();
    });
    

    【讨论】:

    • 首先,transaction() 的文档说A developer-supplied function which will be passed the current data stored at this location (as a JavaScript object). The function should return the new value it would like written (as a JavaScript object). If undefined is returned (i.e. you return with no arguments) the transaction will be aborted and the data at this location will not be modified. 没有关于“猜测”的内容。我的理解是,获取null 意味着它已被某人删除,因为我有一个if 在上面检查null
    • 其次,需要事务来防止竞争条件。对于创建的每个项目,我想只执行一次处理,然后以工作队列模式将其删除。 // process item 注释是完成/启动处理的地方。云端功能和第一版可能都在监听/dropbox中的新项目。
    • 如果该值在本地不存在,事务将尝试使用null。这是预期的行为。如果你确实需要你的交易,你可以通过去掉if (value)检查来修复你的代码。
    • 那么,自从我的处理程序开始运行以来,其他人删除该项目与它仍然存在之间,如何区分呢?它在哪里记录了这是“预期行为”?
    • 这是预期行为并记录在案。交易不是解决此问题的正确工具。尝试工作队列,让工作人员通过事务“声明”这些项目,并在完成后可能删除。但不要将删除作为“声明”的一种方式。请参阅firebase-queue 了解更强大的工作模式。
    猜你喜欢
    • 2017-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-15
    • 1970-01-01
    • 2021-08-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多