Solid.js异步函数异常规范处理:401认证失效集中重定向方案
问题描述
我是Solid.js及前端开发新手,目前面临以下问题:
- 编写了调用服务端API的
UserActions类,采用Cookie身份认证,当Cookie过期时接口返回HTTP 401错误。 - 尝试用
ErrorBoundary捕获异常,但无法捕获onClick事件处理函数中抛出的异常(推测因为异常未发生在渲染阶段)。 - 在
UserActions中调用useNavigate()进行重定向没有效果。
现有UserActions类代码:
export class UserActions { private handleError(response: Response): void { if (response.status === 401) { useNavigate()(`/login?redirect=${encodeURIComponent(location.pathname)}`) // throw new ServiceError(ErrorType.Authentication, 'Please login again'); } } login(email: string, password: string): Promise<User | null> { return fetch('http://localhost:8080/api/login', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email: email, password: password, }), }) .then(response => { this.handleError(response); return response.ok ? response.json() : null; }) } }
使用该类的UserProfile组件示例:
export default function UserProfile() { const context = useContext(SessionContext); const [read, write] = createSignal<string>("Initial value") const clickHandler = async () => { let user = await api.currentUser() write(user.email) }; return ( <AuthenticatedRoute> <div>Hello, {context?.user?.firstName} {context?.user?.lastName}</div><br/> <button onClick={clickHandler}>Refresh</button><br/> <div>Value: {read()}</div> </AuthenticatedRoute> ); }
解决方案
1. 修复useNavigate()在类中无效的问题
Solid.js的hooks只能在组件或其他hooks内部调用,不能直接在类实例方法中使用。推荐用全局事件总线的方式统一处理,避免依赖注入的繁琐:
第一步:创建全局事件工具
// eventBus.ts type EventType = 'auth:401'; const listeners: Record<EventType, Set<() => void>> = { 'auth:401': new Set() }; export const eventBus = { on(event: EventType, callback: () => void) { listeners[event].add(callback); }, off(event: EventType, callback: () => void) { listeners[event].delete(callback); }, emit(event: EventType) { listeners[event].forEach(callback => callback()); } };
第二步:修改UserActions的错误处理
import { eventBus } from './eventBus'; export class UserActions { private handleError(response: Response): void { if (response.status === 401) { eventBus.emit('auth:401'); throw new Error('登录已过期,请重新登录'); } } // 其他API方法保持不变... }
第三步:在根组件监听事件并处理重定向
// App.tsx import { useNavigate, useEffect } from 'solid-js'; import { eventBus } from './eventBus'; export default function App() { const navigate = useNavigate(); useEffect(() => { const handleAuthExpired = () => { // 跳转到登录页并携带当前路径作为重定向参数 navigate(`/login?redirect=${encodeURIComponent(location.pathname)}`); }; eventBus.on('auth:401', handleAuthExpired); // 组件卸载时移除监听 return () => eventBus.off('auth:401', handleAuthExpired); }, [navigate]); // 根组件的路由、上下文等内容... }
2. 统一捕获异步事件中的异常
ErrorBoundary仅能捕获渲染阶段的同步异常,对于点击事件这类异步操作,需要封装通用API请求工具来统一处理错误:
封装API请求工具
// apiClient.ts import { eventBus } from './eventBus'; export async function apiClient<T>(url: string, options: RequestInit = {}): Promise<T> { const response = await fetch(url, { credentials: 'include', // 统一携带Cookie ...options, headers: { 'Content-Type': 'application/json', ...options.headers } }); if (response.status === 401) { eventBus.emit('auth:401'); throw new Error('未授权,请重新登录'); } if (!response.ok) { throw new Error(`请求失败,状态码:${response.status}`); } return response.json() as Promise<T>; }
在UserActions中使用该工具
import { apiClient } from './apiClient'; export class UserActions { login(email: string, password: string): Promise<User | null> { return apiClient<User>('http://localhost:8080/api/login', { method: 'POST', body: JSON.stringify({ email, password }) }).catch(err => { // 可选:处理登录失败的特定逻辑 return null; }); } currentUser(): Promise<User> { return apiClient<User>('http://localhost:8080/api/current-user'); } }
3. 同步更新SessionContext状态
如果用SessionContext维护用户登录状态,可在401事件触发时清空用户信息,避免组件显示过期数据:
// App.tsx import { useNavigate, useEffect, createSignal } from 'solid-js'; import { eventBus } from './eventBus'; import { SessionContext } from './SessionContext'; export default function App() { const navigate = useNavigate(); const [user, setUser] = createSignal<User | null>(null); useEffect(() => { const handleAuthExpired = () => { setUser(null); // 清空用户状态 navigate(`/login?redirect=${encodeURIComponent(location.pathname)}`); }; eventBus.on('auth:401', handleAuthExpired); return () => eventBus.off('auth:401', handleAuthExpired); }, [navigate]); return ( <SessionContext.Provider value={{ user, setUser }}> {/* 路由组件 */} </SessionContext.Provider> ); }
内容的提问来源于stack exchange,提问作者expert
相关产品推荐
相关产品推荐

