问题现象

某页面打开时偶发报错、提示开小差,刷新一次往往又正常。问题时有时无、很难稳定复现。排查发现出错时该页发出的接口请求没有带鉴权头,被后端网关直接返回 401。

背景

App 有一个全局 SDK,启动时负责去换取用户 token 并挂到统一的请求拦截器上,之后所有接口都靠这个拦截器带鉴权头。某次改版调整了启动流程。

根本原因

初始化是异步的,代码却把它当同步用,靠先调 init 再渲染的书写顺序去假设 token 已就绪。异步任务的完成时机由事件循环决定,不由代码行数先后决定,所以这是竞态:只要初始化比首个请求慢,拦截器还没挂上 token,请求就漏带鉴权头。页面组件在挂载时直接发了请求,恰好赶在初始化完成前。

// 问题版:init 没有 await,token 可能还没到
sdk.init();   // 异步,未等待
renderApp();  // 页面已经开始发请求了

解决方案

把初始化变成一个所有请求都能等待的 Promise,请求前先等它就绪;或在请求拦截器里统一兜底,token 未就绪时挂起或短暂重试,而不是裸发。

// 暴露一个 ready promise,初始化真正完成才 resolve
export const ready = sdk.init();
// 组件里:先等 ready 再发请求
useEffect(() => { ready.then(() => http.get("/api/detail")).then(setData); }, []);

测试建议

专门制造慢初始化场景:给 token 接口加延迟、弱网、冷启动首屏,验证首个请求也能带上鉴权头。同时验证后端返回 401 时前端能自动等待就绪后重试一次,而不是直接把错误抛给用户。

经验总结

只要请求依赖某个初始化结果(token、全局配置、上下文),就用一个可等待的信号显式串起来,先等就绪再发,别假设先执行的代码先完成。