25

当我transaction()用来更新位置时,即使该位置有一些数据,该位置的数据也会返回 null 。

transaction() 我在同一位置读取数据后尝试在该位置提供所有数据。

transaction()如果是上述情况,我该如何使用?

4

3 回答 3

30

事务以 Amazon 的 SimpleDB 或数据库分片集群的方式工作。也就是说,它们是“最终一致”而不是保证一致。

因此,当您使用事务时,处理函数可能会使用本地值多次调用(在某些情况下,如果从未检索到它,则为 null),然后再次使用同步值(无论服务器上的什么)调用。

例子:

pathRef.transaction(function(curValue) {

    // this part is eventually consistent and may be called several times

}, function(error, committed, ss) {

    // this part is guaranteed consistent and will match the final value set

});

无论如何,这确实是您必须处理交易的心态。您应该总是期待多次调用,因为第一个事务可能会与另一个更改发生冲突并被拒绝。您不能使用事务的处理方法来获取服务器值(尽管您可以从成功回调中读取它)。

防止本地触发的事件

当事务发生时,会在到达服务器之前触发本地事件以进行延迟补偿。如果事务失败,则本地事件将被恢复(触发更改或删除事件)。

您可以使用事务applyLocally上的属性来覆盖此行为,这会使本地结果变慢,但确保仅在本地触发服务器值。

pathRef.transaction(function(curValue) {

    // this is still called multiple times

}, function(error, committed, ss) {

    // this part is guaranteed consistent and will match the final value set

}, 
    // by providing a third argument of `true`, no local event
    // is generated with the locally cached value.
true);
于 2013-05-03T15:19:40.397 回答
20

您需要遵循以下模式:

var pinRef = firebase.database().ref('vm-pin-generator');
pinRef.transaction(function(oldPin) {
    // Check if the result is NOT NULL:
    if (oldPin != null) {
        return localPinIncrementor(oldPin);
    } else {
        // Return a value that is totally different 
        // from what is saved on the server at this address:
        return 0;
    }
}, function(error, committed, snapshot) {
    if (error) {
        console.log("error in transaction");
    } else if (!committed) {
        console.log("transaction not committed");
    } else {
        console.log("Transaction Committed");
    }
}, true);

Firebase 通常在第一次检索键时返回空值,但在保存它时会检查新值是否与旧值相似。如果没有,firebase 将再次运行整个过程,这一次服务器返回正确的值。

添加一个null检查并返回一个完全意外的值(0在这种情况下)将使 firebase 再次运行循环。

于 2016-08-28T18:07:30.563 回答
0

简单地展示一个示例实现,以使用自定义 upsert 函数详细说明上述@Kato 接受的答案:

  /**
   * Transactional insert or update record
   * @param  {String} type - object type (table or index) to build lookup path
   * @param  {String} id - object ID that will be concat with path for lookup
   * @param  {Object} data - new object (or partial with just edited fields)
   * @return {Object}       new version of object
   */
  const upsert = (type, id, data) => {
    return new Promise((resolve, reject) => {
      if (!type) {
        log.error('Missing db object type')
        reject(new TypeError('Missing db object type'))
      }
      if (!id) {
        log.error('Missing db object id')
        reject(new TypeError('Missing db object id'))
      }
      if (!data) {
        log.error('Missing db data')
        reject(new TypeError('Missing db data'))
      }

      // build path to resource
      const path = `${type}/${id}`
      log.debug(`Upserting record '${path}' to database`)

      try {
        const ref = service.ref(path)
        ref.transaction(record => {
          if (record === null) {
            log.debug(`Creating new record`) // TODO: change to debug
            return data
          } else if (record) {
            log.debug(`Updating existing record`) // TODO: change to debug
            const updatedRecord = Object.assign({}, record, data)
            return updatedRecord
          } else {
            return record
          }
        }, (error, committed, snapshot) => {
          if (error) {
            log.error(`Error upserting record in database`)
            log.error(error.message)
            reject(error)
          } else if (committed) {
            log.debug(`Saved update`)
          } else {
            log.debug(`Record unchanged`)
          }

          if (snapshot) {
            resolve(snapshot.val())
          } else {
            log.info(`No snapshot found in transaction so returning original data object`)
            resolve(data)
          }
        })
      } catch (error) {
        log.error(error)
        reject(error)
      }
    })
  }
于 2018-04-10T13:54:38.017 回答