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

Angular 12双组件同时订阅购物车服务重复生成Firestore购物车问题

问题根源

你遇到的是典型的异步竞态条件问题:两个组件几乎同时调用getCartItems(),进而同时触发getOrCreateCartId()方法。此时第一个调用已经进入创建购物车的异步流程,但还没完成将cartId写入localStorage的操作,第二个调用检测localStorage时cartId仍然为null,就会触发第二次购物车创建逻辑,最终生成两条重复的购物车记录。

解决方案

只需在ShoppingCartService中新增一个待处理Promise缓存,避免重复触发创建逻辑即可,修改后的完整服务代码如下:

import { take } from 'rxjs/operators';
import { AngularFirestore} from '@angular/fire/firestore';
import { Injectable } from '@angular/core';
import { Product } from './models/app.product';
import { ShoppingCart } from './models/shopping-cart';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class ShoppingCartService {
  // 新增:缓存待处理的cartId Promise,避免重复创建
  private cartIdPromise: Promise<string> | null = null;
  
  constructor(private afs: AngularFirestore) { }
  
  private async getOrCreateCartId(): Promise<string>{
    // 如果已经有正在处理的cartId请求,直接返回即可
    if (this.cartIdPromise) return this.cartIdPromise;

    let cartId = localStorage.getItem('cartId');
    if (cartId) {
      this.cartIdPromise = Promise.resolve(cartId);
      return cartId;
    }

    // 将创建请求缓存到cartIdPromise中,后续所有调用都复用这个Promise
    this.cartIdPromise = this.create().then(result => {
      localStorage.setItem('cartId', result.id);
      return result.id;
    });
    return this.cartIdPromise;
  }
  private create() {
    return this.afs.collection('shopping-carts').add({
      dateCreated: new Date().getTime()
    });
  }

  async getCartItems(): Promise<Observable<ShoppingCart[]>>{
    let cartId = await this.getOrCreateCartId();
    return this.afs.collection('shopping-carts').doc(cartId).collection<ShoppingCart>('items').valueChanges();

  }
  async getCart() {
    let cartId = await this.getOrCreateCartId();
    return this.afs.collection('shopping-carts').ref.doc(cartId).get();

  }
  private getItem(cartId: string, productId: string) {
    return this.afs.collection('shopping-carts').doc(cartId).collection('items').doc(productId);
  }

  async updateItemQuantity(product: Product, change: number) {
    let cartId = await this.getOrCreateCartId();
    let item$ = this.getItem(cartId, product.id);
    item$.get().pipe(take(1)).subscribe((item) => {
         item$.set ({ product: product, quantity: ((item.data()?.quantity || 0)  + change) }) 
      });
  }
  async removeFromCart(product: Product) {
    this.updateItemQuantity(product, -1);
  }

  async addToCart(product: Product) {
    this.updateItemQuantity(product, 1);
  }


  getTotalItemsCount(cart: ShoppingCart[]) {
        let count = 0;
        cart.forEach((item: any) => {
            count += item.quantity;
        })
        return count;
    
  }
}

逻辑说明

新增的cartIdPromise会缓存第一次创建购物车的异步请求,后续所有同时调用getOrCreateCartId的逻辑都会复用同一个Promise,只会触发一次购物车创建操作,也只会写入一次cartId到localStorage,完全解决竞态导致的重复创建问题。你的两个组件代码不需要做任何修改,升级服务后即可正常运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 18:39:04