Javascript Firebase cloud函数onUpdate已触发,但不';我不能如愿执行

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集合“用户”中,每个用户文档都有“体重”字段。每当培训师在对特定客户进行身体评估后填写进度表时,客

我目前正在制作一个带有React前端和Firebase后端的web应用程序。这是一个本地健身房的申请,由两部分组成:

  • 为在当地健身房训练的人提供的客户端应用程序
  • 本地健身房教练的教练申请表
当地的健身房为公司提供项目。因此,一家公司需要订阅,该公司的员工可以在当地健身房进行培训,并使用客户端应用程序。跟踪公司员工的个人进度以及整个进度(x公司所有员工损失的总公斤数)非常重要

在Firestore集合“用户”中,每个用户文档都有“体重”字段。每当培训师在对特定客户进行身体评估后填写进度表时,客户用户文档中的体重字段将更新为新体重

在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]
        ]);
      });
  }
});