You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 19:20:25