大多数前端开发日常关注的是页面效果,交互体验,很少考虑到处理百万级别的api请求。当你项目突然爆火,或者使用的用户量突破百万级别时,那些随手写的api调用就会成为系统崩溃的根本原因所在。

一、缓存是前端的第一道防线

每个不必要的API调用都在消耗性能。合理使用缓存能大幅度减少请求数量。

1.1 浏览器缓存

设置合适的Cache-Control头部,让浏览器自动缓存静态资源

// 服务器设置缓存头部
app.get('/api/static-data', (req, res) => {
  res.setHeader('Cache-Control', 'public, max-age=3600'); // 缓存1小时
  res.json(staticData);
});

// 服务器设置缓存头部
app.get('/api/static-data', (req, res) => {
  res.setHeader('Cache-Control', 'public, max-age=3600'); // 缓存1小时
  res.json(staticData);
});

1.2 客户端缓存

使用状态管理工具缓存数据/

// 使用react Query进行数据缓存
import { useQuery } from'@tanstack/react-query';

functionUserProfile({ userId }) {
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
staleTime: 5 * 60 * 1000, // 5分钟内不使用新请求
  });

return<div>{user?.name}</div>;
}

1.3 CDN缓存

静态内容使用CDN边缘节点缓存,减轻源站压力。养成习惯,在发起请求前先问问这个数据是否已经存在

二、控制请求频率:防抖与节流

搜索框每次输入都出发API请求?这是在用前端DDoS自己的后端

2.1 防抖的实现

functiondebounce(func, wait) {
let timeout;
returnfunctionexecutedFunction(...args) {
const later = () => {
clearTimeout(timeout);
      func(...args);
    };
clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// 搜索框使用防抖
const searchInput = document.getElementById('search');
const debouncedSearch = debounce((query) => {
  fetch(`/api/search?q=${query}`)
    .then(response => response.json())
    .then(updateResults);
}, 300);

searchInput.addEventListener('input', (e) => {
  debouncedSearch(e.target.value);
});

2.2 节流的实现

functionthrottle(func, limit) {
let inThrottle;
returnfunction(...args) {
if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
setTimeout(() => inThrottle = false, limit);
    }
  };
}

// 滚动加载使用节流
window.addEventListener('scroll', throttle(() => {
if (nearBottom()) {
    loadMoreItems();
  }
}, 1000));

三、批量请求:减少链接数

同时发起50个请求不如合并成一个

3.1 批量请求示例

// 批量获取用户信息
asyncfunctiongetUsersBatch(userIds) {
// 如果后端支持批量查询
const response = await fetch('/api/users/batch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userIds })
  });
return response.json();
}

// 或者使用Promise.all控制并发
asyncfunctionfetchMultipleResources(resourceIds) {
const batches = [];
for (let i = 0; i < resourceIds.length; i += 10) {
    batches.push(resourceIds.slice(i, i + 10));
  }

const results = [];
for (const batch of batches) {
const batchResults = awaitPromise.all(
      batch.map(id => fetchResource(id))
    );
    results.push(...batchResults);
  }

return results;
}

3.2 GraphQL批量查询

// 一次请求获取所有需要的数据
const USER_QUERY = `
query GetUserData($userId: ID!) {
    user(id: $userId) {
name
      email
posts(limit: 5) {
        title
        createdAt
      }
      friends {
        name
      }
    }
  }
`;

四、后台刷新:不阻塞用户操作

不是所有的API调用都需要阻塞界面渲染

4.1 先展示后刷新模式

functionProductPage({ productId }) {
const [product, setProduct] = useState(cachedProduct);
const [isRefreshing, setIsRefreshing] = useState(false);

  useEffect(() => {
// 先使用缓存数据立即展示
if (cachedProduct) {
      setProduct(cachedProduct);
    }

// 后台刷新最新数据
    setIsRefreshing(true);
    fetch(`/api/products/${productId}`)
      .then(response => response.json())
      .then(newProduct => {
        setProduct(newProduct);
        cacheProduct(newProduct); // 更新缓存
      })
      .finally(() => setIsRefreshing(false));
  }, [productId]);

return (
<div>
<ProductDetailsproduct={product} />
      {isRefreshing && <divclassName="refreshing-indicator">更新中...</div>}
</div>
  );
}

五、优雅降级:有韧性的前端设计

系统总会出问题,关键是如何优雅的处理

5.1 错误处理和重试机制

asyncfunctionfetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;

for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.ok) return response;

// 服务器错误时重试
if (response.status >= 500) {
thrownewError(`Server error: ${response.status}`);
      }

// 客户端错误不重试
thrownewError(`Request failed: ${response.status}`);
    } catch (error) {
      lastError = error;

// 指数退避:等待时间逐渐增加
const delay = Math.pow(2, attempt) * 1000;
awaitnewPromise(resolve =>setTimeout(resolve, delay));
    }
  }

throw lastError;
}

// 使用示例
try {
const data = await fetchWithRetry('/api/data');
  updateUI(data);
} catch (error) {
// 显示缓存数据或友好错误提示
  showCachedData() || showErrorMessage('网络异常,请稍后重试');
}

5.2 离线支持

// 使用Service Worker缓存关键API响应
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/api/critical-data')) {
    event.respondWith(
      caches.match(event.request)
        .then((cached) => {
// 优先返回缓存,同时更新缓存
          const fetchPromise = fetch(event.request)
            .then((response) => {
              caches.open('api-cache')
                .then((cache) => cache.put(event.request, response.clone()));
return response;
            });

return cached || fetchPromise;
        })
    );
  }
});

六、前端监控:提前发现问题

不要等用户投诉才发现问题

6.1 监控API性能

// 封装fetch添加监控
const monitoredFetch = (url, options) => {
const startTime = performance.now();

return fetch(url, options)
    .then(response => {
const duration = performance.now() - startTime;

// 上报性能数据
      reportAPIMetrics({
        url,
        duration,
status: response.status,
timestamp: Date.now()
      });

return response;
    })
    .catch(error => {
// 上报错误
      reportAPIError({
        url,
error: error.message,
timestamp: Date.now()
      });
throw error;
    });
};

// 使用监控版的fetch
monitoredFetch('/api/user-data')
  .then(response => response.json())
  .then(processData);/

七、推动后端API优化

前端开发者也是API设计的参与者

7.1 提出合理的API需求

当发现一个页面需要10个API请求才能渲染的时候,应该推动后端:

  • 提供聚合接口,减少请求次数

  • 支持字段选择,避免传输不必要的数据

  • 实现合理的分页和过滤

// 不好的做法:多个独立请求
const userPromise = fetch('/api/user/1');
const postsPromise = fetch('/api/user/1/posts');
const friendsPromise = fetch('/api/user/1/friends');

// 好的做法:一个聚合请求
const userDataPromise = fetch('/api/user-profile/1?include=posts,friends');

八、实战建议

8.1 优先级管理

// 区分关键和非关键请求
const requestQueue = {
critical: [], // 用户操作相关,立即发送
normal: [],   // 内容加载,正常发送
background: [] // 数据同步,空闲时发送
};

// 使用requestIdleCallback处理后台请求
functionscheduleBackgroundRequests() {
if ('requestIdleCallback'inwindow) {
    requestIdleCallback(() => {
      processBackgroundRequests();
    });
  } else {
// 降级方案
setTimeout(processBackgroundRequests, 1000);
  }
}

8.2 内存管理

// 清理不再需要的缓存
classAPICache{
constructor(maxSize = 100) {
this.cache = new Map();
this.maxSize = maxSize;
  }

set(key, data) {
if (this.cache.size >= this.maxSize) {
// 移除最旧的条目
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
    }
this.cache.set(key, { data, timestamp: Date.now() });
  }

get(key) {
const item = this.cache.get(key);
if (item && Date.now() - item.timestamp < 5 * 60 * 1000) {
return item.data;
    }
this.cache.delete(key);
returnnull;
  }
}

总结

处理海量API请求需要前端开发转变思维:

  • 把每个请求当做宝贵的资源

  • 优先使用缓存,减少不必要的网络通信

  • 控制请求频率,避免突发流量

  • 设计优雅的降级方案,保障基本功能可用

  • 监控性能指标,及时发现瓶颈