Javascript Firebase cloud函数onUpdate已触发,但不';我不能如愿执行
我目前正在制作一个带有React前端和Firebase后端的web应用程序。这是一个本地健身房的申请,由两部分组成:Javascript Firebase cloud函数onUpdate已触发,但不';我不能如愿执行,javascript,node.js,firebase,google-cloud-firestore,google-cloud-functions,Javascript,Node.js,Firebase,Google Cloud Firestore,Google Cloud Functions,我目前正在制作一个带有React前端和Firebase后端的web应用程序。这是一个本地健身房的申请,由两部分组成: 为在当地健身房训练的人提供的客户端应用程序 本地健身房教练的教练申请表 当地的健身房为公司提供项目。因此,一家公司需要订阅,该公司的员工可以在当地健身房进行培训,并使用客户端应用程序。跟踪公司员工的个人进度以及整个进度(x公司所有员工损失的总公斤数)非常重要 在Firestore集合“用户”中,每个用户文档都有“体重”字段。每当培训师在对特定客户进行身体评估后填写进度表时,客
- 为在当地健身房训练的人提供的客户端应用程序
- 本地健身房教练的教练申请表
exports.updateCompanyProgress = functions.firestore
.document("users/{userID}")
.onUpdate((change, context) => {
const previousData = change.before.data();
const data = change.after.data();
if (previousData === data) {
return null;
}
const companyRef = admin.firestore.doc(`/companies/${data.company}`);
const newWeight = data.bodyweight;
const oldWeight = previousData.bodyweight;
const lostWeight = oldWeight > newWeight;
const difference = diff(newWeight, oldWeight);
const currentWeightLost = companyRef.data().weightLostByAllEmployees;
if (!newWeight || difference === 0 || !oldWeight) {
return null;
} else {
const newCompanyWeightLoss = calcNewCWL(
currentWeightLost,
difference,
lostWeight
);
companyRef.update({ weightLostByAllEmployees: newCompanyWeightLoss });
}
});
上面的云函数中有两个简单的函数:
const diff = (a, b) => (a > b ? a - b : b - a);
const calcNewCWL = (currentWeightLost, difference, lostWeight) => {
if (!lostWeight) {
return currentWeightLost - difference;
}
return currentWeightLost + difference;
};
我已经将云函数部署到Firebase来测试它,但我无法让它工作。该函数在用户文档更新时触发,但不会使用新的WeightLostByalEmployees值更新公司文档。这是我第一次使用Firebase云功能,所以变化很大,这是一个新手的错误。您的云功能有几点需要调整:
- 执行
而不是admin.firestore()
admin.firestore
- 您无法通过执行
来获取公司文档的数据。必须调用异步方法companyRef.data()
- 更新公司文档时使用a,并返回此交易返回的承诺(有关此关键方面的更多详细信息,请参阅)
因此,下面的代码应该可以做到这一点 请注意,由于我们使用了一个事务,实际上我们没有实现上面第二个要点的建议。我们使用
transaction.get(companyRef)
代替
您当前的解决方案中有一些我们可以消除的bug 始终
false
相等性检查
使用以下相等性检查确定数据是否未更改:
if (previousData === data) {
return null;
}
这将始终是false
,因为返回的对象更改了。before.data()
和更改了。after.data()
将始终是不同的实例,即使它们包含相同的数据
公司的变化永远不会被处理
虽然这可能是一个罕见的、可能不可能发生的事件,但如果用户的公司发生了变化,您应该将其权重从原始公司的总数中删除,并将其添加到新公司中
同样,当员工离开公司或删除其帐户时,您应该在onDelete
处理程序中从总数中删除他们的权重
处理浮点和
如果你不知道的话,浮点运算有一些小怪癖。以总和为例,0.1+0.2
,对于人类来说,答案是0.3
,但是对于JavaScript和许多语言来说,答案是0.300000000000000004
。有关更多信息,请参阅
不要把你的重量存储在数据库中作为浮点数,考虑把它存储为整数。由于重量通常不是整数(例如9.81kg
),因此应将该值乘以100(对于2个有效数字),然后将其四舍五入到最接近的整数。然后,当您显示它时,您可以将它除以100,或者在适当的十进制符号中拼接
const v = 1201;
console.log(v/100); // -> 12.01
const vString = String(v);
console.log(vString.slice(0,-2) + "." + vString.slice(-2) + "kg"); // -> "12.01kg"
因此,对于总和,0.1+0.2
,您可以将其放大到10+20
,结果是30
console.log(0.1 + 0.2); // -> 0.30000000000000004
console.log((0.1*100 + 0.2*100)/100); // -> 0.3
但是这种策略本身并不是防弹的,因为有些乘法仍然会出现这些错误,比如0.14*100=14.000000000000002
和0.29*100=28.999999999996
。为了剔除这些,我们对乘以的值进行四舍五入
console.log(0.01 + 0.14); // -> 0.15000000000000002
console.log((0.01*100 + 0.14*100)/100); // -> 0.15000000000000002
console.log((Math.round(0.01*100) + Math.round(0.14*100))/100) // -> 0.15
您可以使用以下方法进行比较:
const arr = Array.from({length: 100}).map((_,i)=>i/100);
console.table(arr.map((a) => arr.map((b) => a + b)));
console.table(arr.map((a) => arr.map((b) => (a*100 + b*100)/100)));
console.table(arr.map((a) => arr.map((b) => (Math.round(a*100) + Math.round(b*100))/100)));
因此,我们可以使用以下辅助函数:
function sumFloats(a,b) {
return (Math.round(a * 100) + Math.round(b * 100)) / 100;
}
function sumFloatsForStorage(a,b) {
return (Math.round(a * 100) + Math.round(b * 100));
}
以这种方式处理权重的主要好处是,现在可以使用快捷方式更新值,而不是使用完整的事务。在罕见的情况下,来自同一公司的两个用户发生更新冲突,您可以重试增量或退回到完整事务
低效的数据解析
在当前代码中,可以使用before和after状态的.data()
来获取函数所需的数据。但是,由于您正在提取用户的整个文档,因此最终解析的是文档中的所有字段,而不仅仅是您所需要的bodyweight
和company
字段。您可以使用
与之相比:
const bodyweight = change.after.get("bodyweight"); // parses only "bodyweight"
const company = change.after.get("company"); // parses only "company"
冗余数学
出于某种原因,您正在计算权重之间差值的绝对值,将差值符号存储为布尔值,然后将它们一起使用,将更改应用回总重量损失
以下几行:
const previousData = change.before.data();
const data = change.after.data();
const newWeight = data.bodyweight;
const oldWeight = previousData.bodyweight;
const lostWeight = oldWeight > newWeight;
const difference = diff(newWeight, oldWeight);
const currentWeightLost = companyRef.data().weightLostByAllEmployees;
const calcNewCWL = (currentWeightLost, difference, lostWeight) => {
if (!lostWeight) {
return currentWeightLost - difference;
}
return currentWeightLost + difference;
};
const newWeightLost = calcNewCWL(currentWeightLost, difference, lostWeight);
可替换为:
const newWeight = change.after.get("bodyweight");
const oldWeight = change.before.get("bodyweight");
const deltaWeight = newWeight - oldWeight;
const currentWeightLost = companyRef.get("weightLostByAllEmployees") || 0;
const newWeightLost = currentWeightLost + deltaWeight;
把它全部放在一起
使用事务回退
在罕见的情况下,您会遇到写入冲突,这种变体会退回到传统事务来重新尝试更改
/**
* Increments weightLostByAllEmployees in all documents atomically
* using a transaction.
*
* `arrayOfCompanyRefToDeltaWeightPairs` is an array of company-increment pairs.
*/
function transactionIncrementWeightLostByAllEmployees(db, arrayOfCompanyRefToDeltaWeightPairs) {
return db.runTransaction((transaction) => {
// get all needed documents, then add the update for each to the transaction
return Promise
.all(
arrayOfCompanyRefToDeltaWeightPairs
.map(([companyRef, deltaWeight]) => {
return transaction.get(companyRef)
.then((companyDocSnapshot) => [companyRef, deltaWeight, companyDocSnapshot])
})
)
.then((arrayOfRefWeightSnapshotGroups) => {
arrayOfRefWeightSnapshotGroups.forEach(([companyRef, deltaWeight, companyDocSnapshot]) => {
const currentValue = companyDocSnapshot.get("weightLostByAllEmployees") || 0;
transaction.update(companyRef, {
weightLostByAllEmployees: currentValue + deltaWeight
})
});
});
});
}
exports.updateCompanyProgress = functions.firestore
.document("users/{userID}")
.onUpdate(async (change, context) => {
// "bodyweight" is the weight scaled up by 100
// i.e. "9.81kg" is stored as 981
const oldHundWeight = change.before.get("bodyweight") || 0;
const newHundWeight = change.after.get("bodyweight") || 0;
const oldCompany = change.before.get("company");
const newCompany = change.after.get("company");
const db = admin.firestore();
if (oldCompany === newCompany) {
// company unchanged
const deltaHundWeight = newHundWeight - oldHundWeight;
if (deltaHundWeight === 0) {
return null; // no action needed
}
const companyRef = db.doc(`/companies/${newCompany}`);
await companyRef
.update({
weightLostByAllEmployees: admin.firestore.FieldValue.increment(deltaHundWeight)
})
.catch((error) => {
// if an unexpected error, just rethrow it
if (error.code !== "resource-exhausted")
throw error;
// encountered write conflict, fall back to transaction
return transactionIncrementWeightLostByAllEmployees(db, [
[companyRef, deltaHundWeight]
]);
});
} else {
// company was changed
const batch = db.batch();
const oldCompanyRef = db.doc(`/companies/${oldCompany}`);
const newCompanyRef = db.doc(`/companies/${newCompany}`);
// remove weight from old company
batch.update(oldCompanyRef, {
weightLostByAllEmployees: admin.firestore.FieldValue.increment(-oldHundWeight)
});
// add weight to new company
batch.update(newCompanyRef, {
weightLostByAllEmployees: admin.firestore.FieldValue.increment(newHundWeight)
});
// apply changes
await db.batch()
.catch((error) => {
// if an unexpected error, just rethrow it
if (error.code !== "resource-exhausted")
throw error;
// encountered write conflict, fall back to transaction
return transactionIncrementWeightLostByAllEmployees(db, [
[oldCompanyRef, -oldHundWeight],
[newCompanyRef, newHundWeight]
]);
});
}
});
谢谢你给我最详细的答案。我真的从中学到了一些东西,云功能现在可以完美地工作了。
const newWeight = change.after.get("bodyweight");
const oldWeight = change.before.get("bodyweight");
const deltaWeight = newWeight - oldWeight;
const currentWeightLost = companyRef.get("weightLostByAllEmployees") || 0;
const newWeightLost = currentWeightLost + deltaWeight;
exports.updateCompanyProgress = functions.firestore
.document("users/{userID}")
.onUpdate(async (change, context) => {
// "bodyweight" is the weight scaled up by 100
// i.e. "9.81kg" is stored as 981
const oldHundWeight = change.before.get("bodyweight") || 0;
const newHundWeight = change.after.get("bodyweight") || 0;
const oldCompany = change.before.get("company");
const newCompany = change.after.get("company");
const db = admin.firestore();
if (oldCompany === newCompany) {
// company unchanged
const deltaHundWeight = newHundWeight - oldHundWeight;
if (deltaHundWeight === 0) {
return null; // no action needed
}
const companyRef = db.doc(`/companies/${newCompany}`);
await companyRef.update({
weightLostByAllEmployees: admin.firestore.FieldValue.increment(deltaHundWeight)
});
} else {
// company was changed
const batch = db.batch();
const oldCompanyRef = db.doc(`/companies/${oldCompany}`);
const newCompanyRef = db.doc(`/companies/${newCompany}`);
// remove weight from old company
batch.update(oldCompanyRef, {
weightLostByAllEmployees: admin.firestore.FieldValue.increment(-oldHundWeight)
});
// add weight to new company
batch.update(newCompanyRef, {
weightLostByAllEmployees: admin.firestore.FieldValue.increment(newHundWeight)
});
// apply changes
await db.batch();
}
});
/**
* Increments weightLostByAllEmployees in all documents atomically
* using a transaction.
*
* `arrayOfCompanyRefToDeltaWeightPairs` is an array of company-increment pairs.
*/
function transactionIncrementWeightLostByAllEmployees(db, arrayOfCompanyRefToDeltaWeightPairs) {
return db.runTransaction((transaction) => {
// get all needed documents, then add the update for each to the transaction
return Promise
.all(
arrayOfCompanyRefToDeltaWeightPairs
.map(([companyRef, deltaWeight]) => {
return transaction.get(companyRef)
.then((companyDocSnapshot) => [companyRef, deltaWeight, companyDocSnapshot])
})
)
.then((arrayOfRefWeightSnapshotGroups) => {
arrayOfRefWeightSnapshotGroups.forEach(([companyRef, deltaWeight, companyDocSnapshot]) => {
const currentValue = companyDocSnapshot.get("weightLostByAllEmployees") || 0;
transaction.update(companyRef, {
weightLostByAllEmployees: currentValue + deltaWeight
})
});
});
});
}
exports.updateCompanyProgress = functions.firestore
.document("users/{userID}")
.onUpdate(async (change, context) => {
// "bodyweight" is the weight scaled up by 100
// i.e. "9.81kg" is stored as 981
const oldHundWeight = change.before.get("bodyweight") || 0;
const newHundWeight = change.after.get("bodyweight") || 0;
const oldCompany = change.before.get("company");
const newCompany = change.after.get("company");
const db = admin.firestore();
if (oldCompany === newCompany) {
// company unchanged
const deltaHundWeight = newHundWeight - oldHundWeight;
if (deltaHundWeight === 0) {
return null; // no action needed
}
const companyRef = db.doc(`/companies/${newCompany}`);
await companyRef
.update({
weightLostByAllEmployees: admin.firestore.FieldValue.increment(deltaHundWeight)
})
.catch((error) => {
// if an unexpected error, just rethrow it
if (error.code !== "resource-exhausted")
throw error;
// encountered write conflict, fall back to transaction
return transactionIncrementWeightLostByAllEmployees(db, [
[companyRef, deltaHundWeight]
]);
});
} else {
// company was changed
const batch = db.batch();
const oldCompanyRef = db.doc(`/companies/${oldCompany}`);
const newCompanyRef = db.doc(`/companies/${newCompany}`);
// remove weight from old company
batch.update(oldCompanyRef, {
weightLostByAllEmployees: admin.firestore.FieldValue.increment(-oldHundWeight)
});
// add weight to new company
batch.update(newCompanyRef, {
weightLostByAllEmployees: admin.firestore.FieldValue.increment(newHundWeight)
});
// apply changes
await db.batch()
.catch((error) => {
// if an unexpected error, just rethrow it
if (error.code !== "resource-exhausted")
throw error;
// encountered write conflict, fall back to transaction
return transactionIncrementWeightLostByAllEmployees(db, [
[oldCompanyRef, -oldHundWeight],
[newCompanyRef, newHundWeight]
]);
});
}
});