Firestore事务中如何正确验证文档存在性(避免依赖错误判断)
嗨,这个问题我太有共鸣了!之前在做Firestore事务的时候也踩过这个坑——明明只是想判断文档存不存在,结果getDocument直接抛错,还和其他权限、网络错误混在一起,特别头疼。不过其实Firestore给我们留了精准区分错误的方式,完全不用靠模糊的错误捕获来判断。
核心思路是精准捕获并区分“文档不存在”的特定错误——Firestore对这类错误有明确的标识,我们可以针对性处理,不会和其他异常混淆。下面分不同平台给你具体的实现方式:
1. Web客户端(JavaScript)的写法
在Web端,你可以在事务回调里尝试获取文档,然后通过捕获错误的code属性判断是不是“文档不存在”:
import { runTransaction, doc } from "firebase/firestore"; const targetDoc = doc(db, "your-collection", "target-doc-id"); await runTransaction(db, async (transaction) => { try { const docSnap = await transaction.get(targetDoc); // 文档存在时的逻辑,比如更新数据 if (docSnap.exists()) { transaction.update(targetDoc, { lastAccessed: new Date() }); } } catch (error) { // 精准判断是否为「文档不存在」错误 if (error.code === "not-found") { // 文档不存在,执行写入操作 transaction.set(targetDoc, { name: "New Document", createdAt: new Date() }); } else { // 其他错误(比如权限不足、网络问题),重新抛出让上层处理 throw error; } } });
这里的error.code === "not-found"是关键——这个错误码是Firestore专门用来标识“请求的资源不存在”的,和权限错误(permission-denied)、网络错误(unavailable)等完全区分开,不用担心混淆。
2. Node.js/Admin SDK的写法
如果你用的是后端的Admin SDK,逻辑基本一致,同样通过错误码判断:
const { getFirestore } = require("firebase-admin/firestore"); const db = getFirestore(); const targetDoc = db.collection("your-collection").doc("target-doc-id"); await db.runTransaction(async (transaction) => { try { const docSnap = await transaction.get(targetDoc); if (docSnap.exists) { // 文档存在时的逻辑 transaction.update(targetDoc, { views: docSnap.data().views + 1 }); } } catch (error) { if (error.code === "not-found") { // 文档不存在,写入新数据 transaction.set(targetDoc, { views: 1, createdAt: new Date() }); } else { // 其他异常抛出,不吞掉错误 throw error; } } });
为什么Firestore要这么设计?
可能你会好奇,为什么不直接返回空快照而是抛错?这其实和事务的原子性有关:如果允许事务中获取不存在的文档并返回空快照,可能会出现竞态条件——比如你刚判断“文档不存在”,另一个事务就创建了这个文档,导致你的写入操作覆盖了别人的数据。
而通过抛错的方式,Firestore会自动重试事务(默认最多5次),确保整个“判断-写入”的操作是原子性的,不会出现中间状态的冲突。
额外小提示
如果你确定自己的场景就是要“文档不存在则创建”,完全不用怕重试——即使在重试过程中其他事务创建了文档,后续的重试会进入“文档存在”的分支,不会重复写入,Firestore的事务机制会帮你处理好一致性。
内容的提问来源于stack exchange,提问作者jayOrange

