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
相关产品推荐
相关产品推荐

