mcpField
F
frontend-api-integration-patterns

frontend-api-integration-patterns

github.com/sickn33开发48.7k85 次安装今天更新
代码仓库

关于 frontend-api-integration-patterns

面向前端开发者的 API 集成技能,提供异步状态、请求取消、重试和去重模式,重点避免过期数据与重复调用,并明确处理界面状态。

主要功能

  1. 01集中管理 API 与规范化错误
  2. 02防止异步状态竞态
  3. 03使用 AbortController 取消请求
  4. 04选择性指数退避重试
  5. 05请求防抖与去重

适用场景

  1. 01避免旧响应覆盖当前数据
  2. 02减少搜索输入触发的 API 调用
  3. 03避免多个组件重复请求
  4. 04处理可取消的 ML 预测请求

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/sickn33/agentic-awesome-skills --skill frontend-api-integration-patterns

安装后在 Claude Code 中输入/frontend-api-integration-patterns确认已加载。

详细介绍

前端 API 集成模式

概述

此技能提供可用于生产环境的模式,帮助前端应用与后端 API 集成。

大多数前端问题并非 API 难以调用所致,而是源于异步行为处理不当,进而导致竞态条件、数据过时、请求重复以及用户体验不佳。

此技能关注的是正确性、弹性和用户体验,而不仅仅是让 API 调用正常工作。


何时使用此技能

  • 将前端应用(React、React Native、Vue 等)连接到后端 API
  • 集成 ML/AI 端点(/predict、/recommend)
  • 在 UI 中处理异步数据
  • 修复数据过时、UI 闪烁或重复请求问题
  • 设计可扩展的前端 API 层

核心模式

1. API 层(关注点分离)

集中管理 API 逻辑并规范化错误。

export class ApiError extends Error {
  constructor(message, status, payload = null) {
    super(message);
    this.name = "ApiError";
    this.status = status;
    this.payload = payload;
  }
}

export const apiClient = async (url, options = {}) => {
  const res = await fetch(url, {
    headers: { "Content-Type": "application/json" },
    ...options,
  });

  if (!res.ok) {
    let payload = null;
    try {
      payload = await res.json();
    } catch (_) {}

    throw new ApiError(
      payload?.message || "Request failed",
      res.status,
      payload
    );
  }

  // handle empty responses safely (e.g. 204 No Content)
  if (res.status === 204) return null;

  const text = await res.text();
  return text ? JSON.parse(text) : null;
};

2. 避免竞态的状态管理

防止过时的响应覆盖新数据。

useEffect(() => {
  let cancelled = false;

  const load = async () => {
    try {
      setLoading(true);
      setError(null);

      const result = await getUser();

      if (!cancelled) setData(result);
    } catch (err) {
      if (!cancelled) setError(err.message);
    } finally {
      if (!cancelled) setLoading(false);
    }
  };

  load();

  return () => {
    cancelled = true;
  };
}, []);

对于非 fetch 异步逻辑,请使用取消标记。对于网络请求,优先使用 AbortController。


3. 取消请求(AbortController)

取消正在进行的请求,以避免内存泄漏和过时的状态更新。

useEffect(() => {
  const controller = new AbortController();

  const load = async () => {
    try {
      const data = await getUser({ signal: controller.signal });
      setData(data);
    } catch (err) {
      if (err.name === "AbortError") return;
      setError(err.message);
    }
  };

  load();
  return () => controller.abort();
}, [userId]);

4. 使用指数退避重试

仅重试明确可安全重放的操作,以及经分类的暂时性故障。默认情况下只尝试一次。在实际适配器中,应遵循提供方的 Retry-After 和总截止时间;不要对限流响应启用这种简单的退避策略。POST 预测示例有意不启用重试,除非后端契约能够独立保证重放安全。

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

const fetchWithBackoff = async (fn, retries = 3, delay = 300, replaySafe = false) => {
  try {
    return await fn();
  } catch (err) {
    const isAbort = err.name === "AbortError";
    const isHttpError = typeof err.status === "number";
    const isNetworkFailure = err.code === "NETWORK_ERROR";
    const isRetryable = replaySafe && !isAbort &&
      (isNetworkFailure || (isHttpError && err.status >= 500));

    if (retries <= 0 || !isRetryable) throw err;

    const nextDelay = delay * 2 + Math.random() * 100;
    await sleep(nextDelay);

    return fetchWithBackoff(fn, retries - 1, nextDelay, replaySafe);
  }
};

5. 对 API 调用进行防抖

避免过多的 API 调用(例如搜索输入)。

const useDebounce = (value, delay = 400) => {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const t = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(t);
  }, [value, delay]);

  return debounced;
};

6. 请求去重

避免多个组件重复调用 API。

const inFlight = new Map();

export const dedupedFetch = (key, fn) => {
  if (inFlight.has(key)) return inFlight.get(key);

  const promise = fn().finally(() => inFlight.delete(key));
  inFlight.set(key, promise);
  return promise;
};

示例

示例 1:使用取消机制进行 ML 预测
const controllerRef = useRef(null);

const handlePredict = async (input) => {
  controllerRef.current?.abort();
  const controller = new AbortController();
  controllerRef.current = controller;

  try {
    const result = await fetchWithBackoff(() =>
      apiClient("/predict", {
        method: "POST",
        body: JSON.stringify({ text: input }),
        signal: controller.signal,
      })
    );

    if (!controller.signal.aborted && controllerRef.current === controller) setOutput(result);
  } catch (err) {
    if (err.name === "AbortError") return;
    if (controllerRef.current === controller) setError(err.message);
  }
};

示例 2:防抖搜索
const debouncedQuery = useDebounce(query, 400);

useEffect(() => {
  if (!debouncedQuery) return;

  const controller = new AbortController();

  searchAPI(debouncedQuery, { signal: controller.signal })
    .then(setResults)
    .catch((err) => {
      if (err.name !== "AbortError") {
        setError("Search failed. Please try again.");
      }
    });

  return () => controller.abort();
}, [debouncedQuery]);

示例 3:乐观 UI 更新
const deleteItem = async (id) => {
  const previous = items;

  setItems((curr) => curr.filter((item) => item.id !== id));

  try {
    await apiClient(`/items/${id}`, { method: "DELETE" });
  } catch (err) {
    setItems(previous);
    setError("Delete failed. Please try again.");
  }
};

最佳实践

  • ✅ 在专用层中集中管理 API 逻辑
  • ✅ 使用自定义错误类规范化错误
  • ✅ 始终处理加载、错误和成功状态
  • ✅ 使用 AbortController 取消请求
  • ✅ 仅重试暂时性故障(5xx)
  • ✅ 对由输入驱动的 API 使用防抖
  • ✅ 对相同请求进行去重

反模式

  • ❌ 重试 4xx 错误
  • ❌ 不取消请求(导致内存泄漏)
  • ❌ 容易引发竞态条件的状态更新
  • ❌ 默默吞掉错误
  • ❌ 多个请求共用全局加载/错误状态
  • ❌ 在组件中反复直接调用 API

常见问题

问题: UI 显示过时的数据
解决方案: 使用取消机制,或阻止过时的响应更新状态

问题: 输入时产生过多 API 调用
解决方案: 使用防抖 + 取消机制

问题: 多个组件发出重复请求
解决方案: 使用请求去重

问题: 重试期间服务器过载
解决方案: 使用指数退避

问题: 组件卸载后仍更新状态
解决方案: 在清理过程中使用 AbortController


局限性

  • 这些示例使用原生 JavaScript 模式;使用 React Query、SWR、Apollo、Relay 或类似工具时,请根据相应框架的数据获取库进行调整。
  • 除非后端提供幂等键或其他可安全处理重复请求的契约,否则不要重试非幂等的变更操作。
  • 不要在前端代码中暴露特权 API 密钥;请通过后端代理敏感请求。

更多资源


评论

支持 Markdown