React中useEffect内两次API调用在Safari刷新时仅一次完成的原因
Safari刷新时useEffect中仅一次API调用完成的问题排查与解决
可能的原因
- Safari激进的GET请求缓存:Safari对GET请求的缓存策略比其他浏览器更严格,页面刷新时,若第二个API的响应头允许缓存,Safari可能直接读取本地缓存而不发起新请求,导致你在网络面板看不到请求记录,但实际缓存数据可能不符合预期。
- Token初始化时机延迟:刷新页面时,
AccountContext中的token可能还未完成初始化(比如从localStorage读取token的逻辑是异步的),导致第二个请求发送时token为空。如果后端对无token的请求返回200状态码但空数据,你的错误判断!belt_response.ok不会触发,就会出现请求“完成”但无有效数据的情况,且无错误提示。 - 页面刷新时的异步请求中断:Safari在页面刷新过程中,若第二个请求还在pending状态,浏览器可能会终止该请求,这种中断不会被常规的
catch捕获(除非用AbortController监听),导致看起来请求没执行完成。
解决方案
强制绕过缓存
给第二个GET请求添加时间戳参数,强制Safari发起新请求:const belt_response = await fetch(`/api/belts/all?t=${Date.now()}`, { method: "GET", headers: { token } })也可以让后端设置响应头禁用缓存:
Cache-Control: no-cache, no-store, must-revalidate确保Token就绪后再请求
- 将
token加入useEffect的依赖数组,确保token更新时重新执行请求:useEffect(() => { // fetchData函数 }, [token]) - 在
fetchData开头判断token是否存在,避免无效请求:async function fetchData() { if (!token) return; // 后续请求逻辑 }
- 将
用AbortController管理请求生命周期
避免页面卸载时请求被意外中断,同时捕获中断错误:useEffect(() => { const controller = new AbortController(); const signal = controller.signal; async function fetchData() { try{ const grading_response = await fetch(`/api/examination/grading/${grading_id}`, { method: "GET", headers: { token }, signal }) if (!grading_response.ok) throw new Error("请求失败") const grading_data = await grading_response.json() setGrading(grading_data) const belt_response = await fetch("/api/belts/all", { method: "GET", headers: { token }, signal }) if (!belt_response.ok) throw new Error("请求失败") const belt_data = await belt_response.json() console.log("Belt data:", belt_data) } catch (error) { if (error.name !== 'AbortError') { console.error("请求出错:", error) } } } fetchData() return () => controller.abort(); }, [token])
关于useEffect的理解
你的useEffect用法本身没有偏差,空依赖数组确实会让回调只在组件挂载时执行一次。问题出在Safari的特殊行为和上下文数据的初始化时机,并非对useEffect的概念误解。
内容的提问来源于stack exchange,提问作者Linus
相关产品推荐
相关产品推荐

