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

如何检测Firebase Firestore中是否存在文档,不存在则创建新文档

Firestore 检测文档存在性并创建新文档的正确实现

原代码的问题点

  • 用snapshotChanges()做实时监听,但这里只需要单次查询,没必要保持监听,用get()更高效,还不用手动unsubscribe()
  • 查询后直接创建存在并发漏洞:查询到文档不存在后,可能在执行创建操作前,有其他客户端插入了相同nome的文档,导致重复创建

方案一:基础版(低并发场景适用)

用单次查询判断文档是否存在,不存在则创建,代码更简洁高效:

async create(doc: any, collection: string): Promise<void> {
  try {
    // 执行单次查询,匹配指定nome的文档
    const querySnapshot = await this.db.collection(collection, ref => 
      ref.where('nome', '==', doc.nome)
    ).get().toPromise();

    if (querySnapshot.empty) {
      // 无匹配文档,创建新文档
      const res = await this.db.collection(collection).add({ ...doc });
      console.log('创建成功,文档ID:', res.id);
    } else {
      console.log('该文档已存在,第一个匹配文档ID:', querySnapshot.docs[0].id);
    }
  } catch (err) {
    console.error('操作出错:', err);
  }
}

关键优化点

  • 用get()替代snapshotChanges(),通过toPromise()转为Promise,配合async/await让代码逻辑更清晰
  • 用querySnapshot.empty判断是否有匹配文档,比判断数组长度更直观
  • 统一用try/catch捕获错误,避免分散的then/catch

方案二:高并发安全版

如果你的场景存在多客户端同时操作的可能,需要保证操作的原子性,避免重复创建,推荐以下两种方式:

子方案1:用唯一字段作为文档ID

如果nome是全局唯一的标识,可以直接把它设为文档ID,这样创建时天然避免重复:

async createWithUniqueId(doc: any, collection: string): Promise<void> {
  try {
    const docRef = this.db.collection(collection).doc(doc.nome);
    const docSnapshot = await docRef.get().toPromise();

    if (!docSnapshot.exists) {
      await docRef.set({ ...doc });
      console.log('文档创建成功');
    } else {
      console.log('该文档已存在');
    }
  } catch (err) {
    console.error('操作出错:', err);
  }
}

更简洁的写法,直接用set()的默认行为(文档存在则报错):

async createWithUniqueId(doc: any, collection: string): Promise<void> {
  try {
    // merge: false 是默认值,文档存在时会抛出错误
    await this.db.collection(collection).doc(doc.nome).set({ ...doc }, { merge: false });
    console.log('文档创建成功');
  } catch (err) {
    if ((err as any).code === 'already-exists') {
      console.log('该文档已存在');
    } else {
      console.error('操作出错:', err);
    }
  }
}

子方案2:事务(复杂场景适用)

如果不能用nome作为文档ID,用事务保证查询和创建操作的原子性,中间不会被其他操作打断:

async createWithTransaction(doc: any, collection: string): Promise<void> {
  await this.db.firestore.runTransaction(async (transaction) => {
    // 构建查询条件
    const query = this.db.collection(collection).ref.where('nome', '==', doc.nome);
    const querySnapshot = await transaction.get(query);

    if (querySnapshot.empty) {
      // 创建新文档
      const newDocRef = this.db.collection(collection).ref.doc();
      transaction.set(newDocRef, { ...doc });
      console.log('文档创建成功,ID:', newDocRef.id);
    } else {
      console.log('该文档已存在,第一个匹配ID:', querySnapshot.docs[0].id);
      // 抛出自定义错误终止事务
      throw new Error('文档已存在');
    }
  }).catch((err) => {
    // 只处理非自定义错误
    if (err.message !== '文档已存在') {
      console.error('事务执行失败:', err);
    }
  });
}

内容的提问来源于stack exchange,提问作者xandeq

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 17:39:39