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

