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

如何防抖异步函数?解决Pinia中Firestore查询重复调用异常

解决Pinia中连续调用Firestore查询方法返回异常的问题

问题根源

你遇到的异常主要来自两个核心问题:

  1. onSnapshot是实时监听机制:每次调用getdayAppointments都会创建一个新的Firestore实时监听,旧监听不会自动销毁。连续调用时,多个监听的回调会无序触发,导致appointments被旧查询结果覆盖。
  2. 异步状态管控缺失:loadingAppointments的更新和onSnapshot回调的执行顺序不可控,第二次调用设置loading为true后,第一次的回调可能才完成,把appointments重置为旧数据,同时将loading置为false。

解决方案一:使用一次性查询(非实时更新场景)

如果不需要实时同步数据变化,改用getDocs做一次性查询,同时添加请求锁阻止重复调用:

import { defineStore } from 'pinia';
import {
  collection, getDocs, where, query,
  addDoc, deleteDoc, doc, updateDoc,
} from 'firebase/firestore';

import { db } from '/src/js/firebase';
import { useStoreAuth } from './storeAuth'; // 确保导入Auth Store

export const useStoreAppointments = defineStore('storeAppointments', {
  state: () => {
    return {
      loadingAppointments: false,
      appointments: [],
      transition: '',
      dayAppointments: [],
      selectedDate: today(),
      isFetching: false, // 新增请求锁状态
    };
  },
  actions: {
    async getdayAppointments(appointmentdate) {
      // 正在请求时直接返回,阻止重复调用
      if (this.isFetching) return;

      const storeAuth = useStoreAuth();
      this.loadingAppointments = true;
      this.isFetching = true;

      try {
        const appointmentsCollectionRef = collection(db, 'users', storeAuth.user.id, 'appointments');
        const q = query(appointmentsCollectionRef, where("appointmentdate", "==", appointmentdate));
        const querySnapshot = await getDocs(q);
        
        const fbAppointments = [];
        querySnapshot.forEach((doc) => {
          fbAppointments.push({
            id: doc.id,
            details: doc.data().details,
            bgcolor: doc.data().bgcolor,
            date: doc.data().appointmentdate,
            duration: doc.data().duration,
            time: doc.data().time,
            title: doc.data().patientname,
            moneypaid: parseInt(doc.data().moneypaid),
            currencypaid: doc.data().currencypaid,
            completed: doc.data().completed,
            doctor: doc.data().doctor,
            start: doc.data().start,
            end: doc.data().end,
            split: doc.data().split,
          });
        });
        
        this.appointments = fbAppointments;
      } catch (error) {
        console.error('获取预约数据失败:', error);
      } finally {
        this.loadingAppointments = false;
        this.isFetching = false; // 释放请求锁
      }
    },
  },
});

解决方案二:保留实时监听,管理监听生命周期(实时更新场景)

如果需要实时同步数据变化,要保存每次监听的销毁函数,每次调用新查询前取消旧监听:

import { defineStore } from 'pinia';
import {
  collection, onSnapshot, where, query,
  addDoc, deleteDoc, doc, updateDoc,
} from 'firebase/firestore';

import { db } from '/src/js/firebase';
import { useStoreAuth } from './storeAuth';

export const useStoreAppointments = defineStore('storeAppointments', {
  state: () => {
    return {
      loadingAppointments: false,
      appointments: [],
      transition: '',
      dayAppointments: [],
      selectedDate: today(),
      currentUnsubscribe: null, // 保存当前监听的销毁函数
    };
  },
  actions: {
    getdayAppointments(appointmentdate) {
      const storeAuth = useStoreAuth();
      this.loadingAppointments = true;

      // 取消之前的监听,避免旧回调干扰
      if (this.currentUnsubscribe) {
        this.currentUnsubscribe();
      }

      const appointmentsCollectionRef = collection(db, 'users', storeAuth.user.id, 'appointments');
      const q = query(appointmentsCollectionRef, where("appointmentdate", "==", appointmentdate));
      
      // 保存新的监听销毁函数
      this.currentUnsubscribe = onSnapshot(q, (querySnapshot) => {
        const fbAppointments = [];
        querySnapshot.forEach((doc) => {
          fbAppointments.push({
            id: doc.id,
            details: doc.data().details,
            bgcolor: doc.data().bgcolor,
            date: doc.data().appointmentdate,
            duration: doc.data().duration,
            time: doc.data().time,
            title: doc.data().patientname,
            moneypaid: parseInt(doc.data().moneypaid),
            currencypaid: doc.data().currencypaid,
            completed: doc.data().completed,
            doctor: doc.data().doctor,
            start: doc.data().start,
            end: doc.data().end,
            split: doc.data().split,
          });
        });
        
        this.appointments = fbAppointments;
        this.loadingAppointments = false;
      }, (error) => {
        console.error('监听预约数据失败:', error);
        this.loadingAppointments = false;
      });
    },
    // 可选:Store销毁时取消监听,避免内存泄漏
    destroy() {
      if (this.currentUnsubscribe) {
        this.currentUnsubscribe();
      }
    }
  },
});

额外优化建议

  • 如果是用户快速切换日期导致的连续调用,可以在组件层添加防抖处理(比如用Lodash的debounce),延迟触发查询,避免短时间内多次请求。
  • 确保调用getdayAppointments时storeAuth.user.id已存在,避免因未登录出现undefined错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 01:15:27