Angular应用中Firebase setDoc无报错挂起,添加延迟后可正常执行
Firebase setDoc 偶发无报错挂起,休眠1秒后必成功的竞态问题
问题现象
- 调用
setDoc写入Firestore文档时,约50%概率无任何报错但程序停滞挂起 - 在
setDoc调用前手动休眠1秒(await sleep(1000)),文档写入100%成功,疑似存在竞态条件 await createUserWithEmailAndPassword(...)始终执行正常,无异常- 当前正在从Firebase compat库升级到模块化SDK,升级前compat库的文档写入功能完全正常
Cloud Firestore 规则
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { // allow read, write: if request.time < timestamp.date(2023, 12, 11); allow read, write: if true; } } }
相关代码
注册组件代码
import { AuthService } from '../services/auth.service'; import { Router } from '@angular/router'; import { User } from '../models/user.model'; import { environment } from '../../environments/environment'; ... async signupUser() { try { let emailLower = this.signupForm.value.email.toLowerCase(); const user: User = { id: emailLower, email: this.signupForm.value.email, emailLower: emailLower, accountType: 'endUser', displayName: this.signupForm.value.displayName, displayNameLower: this.signupForm.value.displayName.toLowerCase(), newsletter: this.signupForm.value.newsletter, }; const password = this.signupForm.value.password; // console.log('sleeping...'); // await this.sleep(1000); // console.log('woke up'); const result = await this.authService.signup(user, password); if (result == null) { this.router.navigate(['/home']); } else if (result.isValid === false) { this.errorMessage = this.getFriendlyErrorMessage(result.code); } } catch (error) { console.error('Error during signup:', error); } }
AuthService 代码(包含setDoc调用)
import { createUserWithEmailAndPassword, getAuth, sendEmailVerification, sendPasswordResetEmail, signInWithEmailAndPassword, signOut } from 'firebase/auth'; import { addDoc, Firestore, collection, doc, setDoc } from '@angular/fire/firestore'; ... @Injectable({ providedIn: 'root' }) export class AuthService { constructor(private router: Router, private firestore: Firestore, private http: HttpClient, private fns: Functions) {...} async signup(user: User, password: string): Promise<{ isValid: boolean; code?: string; message?: string } | null> { const auth = getAuth(); try { const result = await createUserWithEmailAndPassword(auth, user.email, password); const userDoc = doc(this.firestore, 'users', user.emailLower); try { await setDoc(userDoc, user); console.log('auth setDoc: success'); } catch (error) { console.log('auth setDoc: error:', error); return null; } if (result && result.user) { await sendEmailVerification(result.user); } else { console.log("Auth Service: User creation succeeded but unable to send verification email. No user in result."); } return null; // success } catch (error) { console.log('auth service: signup error:', error); return { isValid: false, code: error.code, message: error.message }; } } }
解决方案
核心原因
升级到模块化SDK后,Firebase Auth创建用户后,Auth状态同步到Firestore的身份验证上下文存在延迟,导致setDoc调用时,Firestore还未获取到最新的用户身份,从而出现无响应挂起(而非权限报错,因为规则已放开全读写)。
修复方案一:等待Auth状态稳定
在调用setDoc前,通过onAuthStateChanged监听,确保Auth状态完成初始化:
async signup(user: User, password: string): Promise<{ isValid: boolean; code?: string; message?: string } | null> { const auth = getAuth(); try { const result = await createUserWithEmailAndPassword(auth, user.email, password); // 等待auth.currentUser完全初始化 await new Promise(resolve => { const unsubscribe = auth.onAuthStateChanged(authUser => { if (authUser) { unsubscribe(); resolve(authUser); } }); }); const userDoc = doc(this.firestore, 'users', user.emailLower); await setDoc(userDoc, user); console.log('auth setDoc: success'); if (result.user) { await sendEmailVerification(result.user); } else { console.log("Auth Service: User creation succeeded but unable to send verification email. No user in result."); } return null; } catch (error) { console.log('auth service: signup error:', error); return { isValid: false, code: error.code, message: error.message }; } }
修复方案二:使用用户UID作为文档ID(推荐)
改用Firebase Auth生成的用户UID作为文档ID,符合最佳实践的同时,减少身份同步延迟影响:
async signup(user: User, password: string): Promise<{ isValid: boolean; code?: string; message?: string } | null> { const auth = getAuth(); try { const result = await createUserWithEmailAndPassword(auth, user.email, password); // 使用用户UID作为文档ID const userDoc = doc(this.firestore, 'users', result.user.uid); // 更新user对象的id字段 user.id = result.user.uid; await setDoc(userDoc, user); console.log('auth setDoc: success'); if (result.user) { await sendEmailVerification(result.user); } else { console.log("Auth Service: User creation succeeded but unable to send verification email. No user in result."); } return null; } catch (error) { console.log('auth service: signup error:', error); return { isValid: false, code: error.code, message: error.message }; } }
可选补充:添加超时处理
为setDoc添加超时机制,避免程序无限挂起:
// 在setDoc调用处替换为以下代码 const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('setDoc timed out')), 5000) ); await Promise.race([setDoc(userDoc, user), timeoutPromise]);
内容的提问来源于stack exchange,提问作者Ryan Loggerythm
相关产品推荐
相关产品推荐

