Firestore事务中更新操作为何无法捕获文档不存在错误?
Firestore事务中Update操作的NotFound错误捕获问题
问题原因
Firestore事务的回调函数中,tx.Update方法并不会立即验证目标文档是否存在——它只是将更新操作加入事务的待执行队列。实际的文档存在性检查、操作执行都在回调函数执行完毕,事务提交阶段由Firestore服务端处理。这就是为什么你在回调里的tx.Update调用永远不会进入错误分支,而错误会在RunTransaction方法返回时才抛出。
解决方案
方案1:在外层处理错误(无额外读操作)
直接在RunTransaction的返回错误中判断是否为用户文档不存在的情况,返回自定义错误信息,完全不需要额外的读操作,符合你的需求:
// Fetch document references userRef := s.firestore.Collection("users").Doc(params.UserID) dashboardRef := s.firestore.Collection("dashboards").Doc(params.DashboardID) // Begin Firestore transaction if err := s.firestore.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error { // User data to update userUpdates := []firestore.Update{ { Path: "sharedWithMeDashboardIDs", Value: firestore.ArrayUnion(params.DashboardID), }, } fmt.Println("user update") // 这里无需处理NotFound错误,因为Update不会在回调内返回该错误 if err := tx.Update(userRef, userUpdates); err != nil { // 这里仅处理权限错误、参数错误等非文档不存在类错误 return err } // ...rest of code fmt.Println("called") return nil }); err != nil { // 在外层解析并处理错误 if status.Code(err) == codes.NotFound { // 验证错误是否指向目标用户文档 errMsg := err.Error() targetDocPath := "users/" + params.UserID if strings.Contains(errMsg, targetDocPath) { return nil, fmt.Errorf("user %s does not exist", params.UserID) } // 如需处理其他文档(如dashboard)的NotFound错误,可在此添加逻辑 } return nil, err }
方案2:在回调内先读文档(有额外读操作)
如果一定要在回调函数内部处理文档不存在的错误,就必须先通过tx.Get读取用户文档,提前检查存在性:
if err := s.firestore.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error { // 先读取用户文档,确认是否存在 userDoc, err := tx.Get(userRef) if err != nil { if status.Code(err) == codes.NotFound { return fmt.Errorf("user %s does not exist", params.UserID) } return err } // 文档存在后执行更新操作 userUpdates := []firestore.Update{ { Path: "sharedWithMeDashboardIDs", Value: firestore.ArrayUnion(params.DashboardID), }, } if err := tx.Update(userRef, userUpdates); err != nil { return err } // ...rest of code return nil }); err != nil { return nil, err }
注意事项
- 即使将重试次数设为1,事务的提交验证逻辑依然在回调执行后,所以回调内的
tx.Update不会抛出NotFound错误。 - 方案1是更高效的选择,避免了额外的读操作,只是错误处理逻辑从回调内移到了外层。
内容的提问来源于stack exchange,提问作者Adam Johnston
相关产品推荐
相关产品推荐

