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

SvelteKit async load失效,Firebase onAuthStateChanged无法阻塞执行

问题描述

基于SvelteKit搭建网站时采用Firebase作为身份认证服务提供方,为实现页面访问鉴权、保护需要登录才能访问的页面,在__layout.svelte中编写认证状态监听器时,始终无法将用户认证状态从context module代码块传递到普通script代码块。

原有问题实现代码(文件:__layout.svelte):

<script lang="ts" context="module">
    import type { User } from 'firebase/auth';
    import { onAuthStateChanged } from "firebase/auth";
    import { auth } from "$lib/scripts/firebaseStart";

    export async function load() {
        let authUser: User|boolean = await getAuthUser();
        async function getAuthUser() {
            await onAuthStateChanged(auth, (user) => {
                if (user) {
                    authUser = user;
                    console.log(authUser); // 可以正常打印预期的authUser
                } else {
                    authUser = false;
                }
            });
            return authUser;
        }

        return {
            props: { authUser }
        };
    }
</script>

<script lang="ts">
    export let authUser: User|boolean;
    console.log(authUser); // 打印undefined,且执行时间早于context模块内的日志
</script>

实际运行现象:普通script标签内的代码会在context=module块返回props之前执行,打印authUser得到undefined,且该日志的执行顺序早于context模块内的日志输出,需要实现context模块代码阻塞执行,正确完成用户认证状态传递。

问题原因

核心错误是对onAuthStateChanged方法的理解有误:

  • onAuthStateChanged是监听器注册方法,不是Promise对象,它的返回值是用于取消监听的unsubscribe函数,直接对其使用await不会等待回调触发,注册完监听逻辑就会立刻继续向下执行
  • 原有代码中getAuthUser函数在监听器注册完成后就直接返回了此时还未赋值的authUser(初始值为undefined),等后续监听器触发拿到用户状态时,load函数早就已经把undefined作为props传给了普通script块,才会出现执行顺序错乱、值为undefined的问题。
修复方案
  • 将首次获取认证状态的逻辑包装为Promise,等待监听器第一次触发拿到有效状态后再resolve,同时拿到状态后立刻取消这次临时监听,避免内存泄漏
  • 长期的认证状态监听移到客户端挂载生命周期中注册,处理后续用户登录、登出的状态变动,组件销毁时记得卸载监听器
  • 增加加载态兜底,避免认证状态未返回时出现页面内容闪屏

修复后的__layout.svelte代码如下:

<script lang="ts" context="module">
    import type { User } from 'firebase/auth';
    import { onAuthStateChanged } from "firebase/auth";
    import { auth } from "$lib/scripts/firebaseStart";

    export async function load() {
        // 包装首次认证状态获取逻辑为可等待的Promise
        const authUser = await new Promise<User | false>((resolve) => {
            // 注册一次性监听,拿到状态后立刻取消
            const unsubscribe = onAuthStateChanged(auth, (user) => {
                unsubscribe();
                resolve(user ?? false);
            });
        });

        return {
            props: { authUser }
        };
    }
</script>

<script lang="ts">
    import { onMount } from 'svelte';
    export let authUser: User | false;

    // 初始状态可直接用于首屏鉴权判断
    console.log('初始认证状态:', authUser);

    onMount(() => {
        // 客户端挂载后注册长期状态监听,处理登录/登出的实时变动
        const unsubscribe = onAuthStateChanged(auth, (user) => {
            authUser = user ?? false;
            // 可在此处添加路由守卫逻辑:若当前访问受保护页面且用户未登录,跳转至登录页
        });

        // 组件销毁时卸载监听器
        return () => unsubscribe();
    });
</script>

<!-- 路由内容渲染,增加加载态避免闪屏 -->
{#if authUser === undefined}
    <div class="loading">认证状态加载中...</div>
{:else}
    <slot />
{/if}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 18:15:59