当我使用 transaction()
更新某个位置时,该位置的数据返回null ,即使该位置包含某些数据。
我在同一位置读取数据后尝试了transaction()
,因为它在该位置提供了所有数据。
如果情况与上述类似,我如何使用transaction()
?
答案 0 :(得分:26)
事务以Amazon SimpleDB或分片数据库集的方式工作。也就是说,它们“最终是一致的”而不是保证一致。
因此,当您使用事务时,处理函数可能会被多次调用一个本地值(在某些情况下,如果它从未被检索过,则为null),然后再次使用同步值(服务器上的任何内容)。 / p>
示例:
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
属性on transactions来覆盖此行为,这会使本地结果变慢但确保仅在本地触发服务器值。
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);
答案 1 :(得分:13)
您需要遵循以下模式:
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再次运行循环。
答案 2 :(得分:0)
简单地展示一个示例实现来详细说明@Kato接受上面的自定义upsert函数的答案:
/**
* 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)
}
})
}