[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-6e434ac1-49fc-48e3-a650-52223cd04fe2":3,"$f0izaoofbPn-zrfE6O7ilsb28Rn7m5hoZIML_9ptsT-o":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"6e434ac1-49fc-48e3-a650-52223cd04fe2","frontend-api-integration-patterns","适用于将前端应用程序与后端API集成的生产就绪模式，包括处理竞态条件、请求取消、重试策略、错误标准化和UI状态管理。","cat_coding_frontend","mod_coding","sickn33,coding","---\nname: frontend-api-integration-patterns\ndescription: \"Production-ready patterns for integrating frontend applications with backend APIs, including race condition handling, request cancellation, retry strategies, error normalization, and UI state management.\"\ncategory: frontend\nrisk: safe\nsource: community\ndate_added: \"2026-04-23\"\nauthor: avij1109\ntags:\n  - frontend\n  - api-integration\n  - javascript\n  - react\n  - async\ntools:\n  - claude\n  - cursor\n  - gemini\n  - codex\n---\n\n# Frontend API Integration Patterns\n\n## Overview\n\nThis skill provides production-ready patterns for integrating frontend applications with backend APIs.\n\nMost frontend issues are not caused by APIs being difficult to call, but by **incorrect handling of asynchronous behavior**—leading to race conditions, stale data, duplicated requests, and poor user experience.\n\nThis skill focuses on **correctness, resilience, and user experience**, not just making API calls work.\n\n---\n\n## When to Use This Skill\n\n* Connecting frontend apps (React, React Native, Vue, etc.) to backend APIs\n* Integrating ML\u002FAI endpoints (`\u002Fpredict`, `\u002Frecommend`)\n* Handling asynchronous data in UI\n* Fixing stale data, flickering UI, or duplicate requests\n* Designing scalable frontend API layers\n\n---\n\n## Core Patterns\n\n### 1. API Layer (Separation of Concerns)\n\nCentralize API logic and normalize errors.\n\n```js id=\"k1m7r2\"\nexport class ApiError extends Error {\n  constructor(message, status, payload = null) {\n    super(message);\n    this.name = \"ApiError\";\n    this.status = status;\n    this.payload = payload;\n  }\n}\n\nexport const apiClient = async (url, options = {}) => {\n  const res = await fetch(url, {\n    headers: { \"Content-Type\": \"application\u002Fjson\" },\n    ...options,\n  });\n\n  if (!res.ok) {\n    let payload = null;\n    try {\n      payload = await res.json();\n    } catch (_) {}\n\n    throw new ApiError(\n      payload?.message || \"Request failed\",\n      res.status,\n      payload\n    );\n  }\n\n  \u002F\u002F handle empty responses safely (e.g. 204 No Content)\n  if (res.status === 204) return null;\n\n  const text = await res.text();\n  return text ? JSON.parse(text) : null;\n};\n```\n\n---\n\n### 2. Race-Safe State Management\n\nPrevent stale responses from overwriting fresh data.\n\n```js id=\"y7p4ha\"\nuseEffect(() => {\n  let cancelled = false;\n\n  const load = async () => {\n    try {\n      setLoading(true);\n      setError(null);\n\n      const result = await getUser();\n\n      if (!cancelled) setData(result);\n    } catch (err) {\n      if (!cancelled) setError(err.message);\n    } finally {\n      if (!cancelled) setLoading(false);\n    }\n  };\n\n  load();\n\n  return () => {\n    cancelled = true;\n  };\n}, []);\n```\n\n> Use a cancellation flag for non-fetch async logic. For network requests, prefer AbortController.\n\n---\n\n### 3. Request Cancellation (AbortController)\n\nCancel in-flight requests to avoid memory leaks and stale updates.\n\n```js id=\"l9x2pw\"\nuseEffect(() => {\n  const controller = new AbortController();\n\n  const load = async () => {\n    try {\n      const data = await getUser({ signal: controller.signal });\n      setData(data);\n    } catch (err) {\n      if (err.name === \"AbortError\") return;\n      setError(err.message);\n    }\n  };\n\n  load();\n  return () => controller.abort();\n}, [userId]);\n```\n\n---\n\n### 4. Retry with Exponential Backoff\n\nRetry only transient failures (5xx or network errors).\n\n```js id=\"8n3zcf\"\nconst sleep = (ms) => new Promise((r) => setTimeout(r, ms));\n\nconst fetchWithBackoff = async (fn, retries = 3, delay = 300) => {\n  try {\n    return await fn();\n  } catch (err) {\n    const isAbort = err.name === \"AbortError\";\n    const isHttpError = typeof err.status === \"number\";\n    const isRetryable = !isAbort && (!isHttpError || err.status >= 500);\n\n    if (retries \u003C= 0 || !isRetryable) throw err;\n\n    const nextDelay = delay * 2 + Math.random() * 100;\n    await sleep(nextDelay);\n\n    return fetchWithBackoff(fn, retries - 1, nextDelay);\n  }\n};\n```\n\n---\n\n### 5. Debounced API Calls\n\nAvoid excessive API calls (e.g., search inputs).\n\n```js id=\"i2r7wq\"\nconst useDebounce = (value, delay = 400) => {\n  const [debounced, setDebounced] = useState(value);\n\n  useEffect(() => {\n    const t = setTimeout(() => setDebounced(value), delay);\n    return () => clearTimeout(t);\n  }, [value, delay]);\n\n  return debounced;\n};\n```\n\n---\n\n### 6. Request Deduplication\n\nPrevent duplicate API calls across components.\n\n```js id=\"x8v4km\"\nconst inFlight = new Map();\n\nexport const dedupedFetch = (key, fn) => {\n  if (inFlight.has(key)) return inFlight.get(key);\n\n  const promise = fn().finally(() => inFlight.delete(key));\n  inFlight.set(key, promise);\n  return promise;\n};\n```\n\n---\n\n## Examples\n\n### Example 1: ML Prediction with Cancellation\n\n```js id=\"n5q2pt\"\nconst controllerRef = useRef(null);\n\nconst handlePredict = async (input) => {\n  controllerRef.current?.abort();\n  controllerRef.current = new AbortController();\n\n  try {\n    const result = await fetchWithBackoff(() =>\n      apiClient(\"\u002Fpredict\", {\n        method: \"POST\",\n        body: JSON.stringify({ text: input }),\n        signal: controllerRef.current.signal,\n      })\n    );\n\n    setOutput(result);\n  } catch (err) {\n    if (err.name === \"AbortError\") return;\n    setError(err.message);\n  }\n};\n```\n\n---\n\n### Example 2: Debounced Search\n\n```js id=\"w4z8yn\"\nconst debouncedQuery = useDebounce(query, 400);\n\nuseEffect(() => {\n  if (!debouncedQuery) return;\n\n  const controller = new AbortController();\n\n  searchAPI(debouncedQuery, { signal: controller.signal })\n    .then(setResults)\n    .catch((err) => {\n      if (err.name !== \"AbortError\") {\n        setError(\"Search failed. Please try again.\");\n      }\n    });\n\n  return () => controller.abort();\n}, [debouncedQuery]);\n```\n\n---\n\n### Example 3: Optimistic UI Update\n\n```js id=\"q2k9hz\"\nconst deleteItem = async (id) => {\n  const previous = items;\n\n  setItems((curr) => curr.filter((item) => item.id !== id));\n\n  try {\n    await apiClient(`\u002Fitems\u002F${id}`, { method: \"DELETE\" });\n  } catch (err) {\n    setItems(previous);\n    setError(\"Delete failed. Please try again.\");\n  }\n};\n```\n\n---\n\n## Best Practices\n\n* ✅ Centralize API logic in a dedicated layer\n* ✅ Normalize errors using a custom error class\n* ✅ Always handle loading, error, and success states\n* ✅ Use AbortController for request cancellation\n* ✅ Retry only transient failures (5xx)\n* ✅ Use debouncing for input-driven APIs\n* ✅ Deduplicate identical requests\n\n---\n\n## Anti-Patterns\n\n* ❌ Retrying 4xx errors\n* ❌ No request cancellation (memory leaks)\n* ❌ Race-condition-prone state updates\n* ❌ Swallowing errors silently\n* ❌ Global loading\u002Ferror state for multiple requests\n* ❌ Calling APIs directly inside components repeatedly\n\n---\n\n## Common Pitfalls\n\n**Problem:** UI shows stale data\n**Solution:** Use cancellation or guard against outdated responses\n\n**Problem:** Too many API calls on input\n**Solution:** Use debouncing + cancellation\n\n**Problem:** Duplicate requests from multiple components\n**Solution:** Use request deduplication\n\n**Problem:** Server overload during retry\n**Solution:** Use exponential backoff\n\n**Problem:** State updates after component unmount\n**Solution:** Use AbortController cleanup\n\n---\n\n## Limitations\n\n* These examples use vanilla JavaScript patterns; adapt them to your framework's data-fetching library when using React Query, SWR, Apollo, Relay, or similar tools.\n* Do not retry non-idempotent mutations unless the backend provides idempotency keys or another duplicate-safe contract.\n* Do not expose privileged API keys in frontend code; proxy sensitive requests through a backend.\n\n---\n\n## Additional Resources\n\n* https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FAbortController\n* https:\u002F\u002Freact.dev\n* https:\u002F\u002Faxios-http.com\n\n---\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,56,1817,"2026-05-16 13:19:22",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"编程开发","coding","mdi-code-braces","代码生成、调试、审查，提升开发效率",2,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"前端开发","frontend","mdi-language-html5","HTML\u002FCSS\u002FJavaScript\u002F框架相关",1,96,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"9db005a9-d3d9-432a-a9e7-6f10bd53f2ec","1.0.0","frontend-api-integration-patterns.zip",3204,"uploads\u002Fskills\u002F6e434ac1-49fc-48e3-a650-52223cd04fe2\u002Ffrontend-api-integration-patterns.zip","d255a1537ebc55deb9dc27c6278430a6bcca3ce495977e8c8f447da374259834","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":7730}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]