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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 12:55:55