问题现象
某页面打开时偶发报错、提示开小差,刷新一次往往又正常。问题时有时无、很难稳定复现。排查发现出错时该页发出的接口请求没有带鉴权头,被后端网关直接返回 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、全局配置、上下文),就用一个可等待的信号显式串起来,先等就绪再发,别假设先执行的代码先完成。