Skip to content

IOS Safari 时序问题导致刚进入的时候发出了空请求 ​

问题现象 ​

在 iOS Safari 浏览器中,页面首次加载时会发出一个异常的空请求:

text
URL: https://hyperhuman.deemos.com/api/task/cards
状态: —
来源: —
初始化程序: 557.4e48380e.js:2:1114582
请求头:
Accept: application/json, text/plain, /
Content-Type: application/json
Origin: https://hyper3d.ai
Referer: https://hyper3d.ai/

该请求没有正确的 method 和 payload,导致请求失败。但奇怪的是:

  1. 代码中的错误处理没有捕获到这个异常
  2. 页面一直保持 loading 状态
  3. 切换 tab 重新发起请求时一切正常
  4. 只有这一个 API 有这个问题

问题分析 ​

通过代码分析,发现这是一个典型的 React 组件初始化时序问题:

  1. 组件初始化时序
js
useDebounceEffect(() => {
    if (cancelRequest) {
        cancelRequest.cancel('CardType changed');
    }
    setAllLoaded(false)
    setIsLoading(true)
    pageRef.current = 0
    if (cardsType === 'Story') {
        // ...
    } else {
        requestAnimationFrame(() => {
            setCards([])
        })
        setGalleryStatus('DEFAULT')
        if (haveSearchTab) {
            throttleFetchSearchCard()
        } else {
            throttleFetch()
        }
        setShowStorySidebar(true)
    }
}, [cardsType, selectedTagKeys, haveSearchTab], 100)
  1. 错误处理不完整

在 iOS Safari 中,组件的初始化时序与其他浏览器有细微差异,导致请求可能在必要的状态初始化之前就发出。

解决方案 ​

  1. 添加初始化状态检查
javascript
const [isInitialized, setIsInitialized] = useState(false);

useEffect(() => {
  const initialize = async () => {
    await new Promise(resolve => setTimeout(resolve, 100));
    setIsInitialized(true);
  };
  initialize();
}, []);

useDebounceEffect(() => {
  if (!isInitialized) return;
  // ... 原有逻辑
}, [cardsType, selectedTagKeys, haveSearchTab, isInitialized], 100);
  1. 完善错误处理
javascript
const fetchCard = async (retryCount = 3) => {
  try {
    if (!cardsType || !pageRef.current) {
      await new Promise(resolve => setTimeout(resolve, 100));
      return fetchCard(retryCount);
    }

    const fetchRes = await getCards({ /*...*/ });

    if (!fetchRes || !fetchRes.data) {
      if (retryCount > 0) {
        await new Promise(resolve => setTimeout(resolve, 1000));
        return fetchCard(retryCount - 1);
      }
      throw new Error('Empty response received');
    }
    
    // 处理响应...
  } catch (error) {
    // 错误处理...
  } finally {
    setIsLoading(false);
  }
};

关键点总结 ​

  1. iOS Safari 的组件初始化时序可能与其他浏览器不同
  2. 需要确保所有必要的状态都已初始化后再发起请求
  3. 添加适当的重试机制
  4. 完善错误处理,覆盖所有异常情况
  5. 确保 loading 状态能正确重置

经验教训 ​

  1. 在处理跨浏览器兼容性时,需要特别注意初始化时序问题
  2. 请求处理要遵循"防御性编程"原则,处理所有可能的异常情况
  3. 添加合适的重试机制和状态重置逻辑
  4. 在开发时多关注不同浏览器的行为差异

这个问题也提醒我们,在前端开发中要特别注意浏览器兼容性和异步操作的时序问题,同时保持良好的错误处理实践。