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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 22:27:20