האפליקציה שלך מוכנה.יש לך backend שעושה כמה דברים קסומים ולאחר מכן חושף כמה נתונים באמצעות API. יש לך frontend שמצרך את האפליקציה הזו ומציג את הנתונים למשתמש.אתה משתמש ב- Fetch API כדי לבצע בקשות אל backend שלך, ואז לעבד את התשובה ולעדכן את המשתמש. ובכן, בפיתוח, כן. אז, אתה מפעיל את האפליקציה שלך לייצור. והדברים המוזרים מתחילים לקרות. רוב הזמן, הכל נראה בסדר, אבל לפעמים, בקשות נכשלות. רשת היא בלתי צפויה, ואתה צריך להיות מוכן לכך. עדיף שיש לך תשובות לשאלות אלה: מה קורה כאשר הרשת היא איטית או לא אמין? מה קורה כאשר backend הוא למטה או מחזיר שגיאה? אם אתה צורך API חיצוניים, מה קורה כאשר אתה פוגע בגבול המהירות ולהיות חסום? איך אתה מתמודד עם התסריטים האלה בהצלחה ומספק חוויה משתמש טובה? בכנות, vanilla Fetch אינו מספיק כדי להתמודד עם תסריטים אלה. אתה צריך להוסיף הרבה קוד boilerplate כדי להתמודד עם שגיאות, retries, timeouts, caching, וכו 'זה יכול במהירות להיות מבולבל וקשה לשמור. במאמר זה, נבחן כיצד להפוך את בקשות האספקה שלך מוכנות לייצור באמצעות ספריה בשם אנחנו רוצים : ffetch לבנות גיבוי עם Node.js ו- Express עם כמה נקודות קצה בנה פרנטנד שמחקר את נקודות הקצה האלה עם vanilla JavaScript באמצעות API Fetch להפוך את הקצה האחורי פשטני כדי לסימולציה של תסריטים בעולם האמיתי ראה כיצד בקשות איסוף יכולות להיכשל וכיצד להתמודד עם כישלונות אלה להציג ffetch כדי להקל ולשפר את ניהול בקשות fetch ה Boilerplate אנו נבנה את הגבולות של רשימת משימות פשוטה למשתמשים מרובים.החלק האחורי יחשוף נקודות קצה RESTful כדי ליצור, לקרוא, לעדכן ולמחוק משתמשים ומשימות, ולהקצות משימות למשתמשים.החלק הקדמי ישאל את החלק האחורי כדי לקבל את הנתונים האחרונים ולהציג אותם למשתמש. אחורי נשתמש ב-Node.js וב-Express כדי לבנות את האחורי. נשתמש גם באחסון נתונים פשוט בזיכרון כדי לשמור על דברים פשוטים. interface User { id: number; // Unique identifier name: string; // Full name email: string; // Email address } export interface Task { id: number; // Unique identifier title: string; // Short task title description?: string; // Optional detailed description priority: "low" | "medium" | "high"; // Task priority } אנו יוצרים את נקודות הסוף הבאות: GET /users: קבל את כל המשתמשים (חזיר קבוצה של מזהים למשתמש) POST /users: יצירת משתמש חדש (חזיר את מזהה המשתמש שנוצר) GET /users/:id: Get a user by id (חזיר את אובייקט המשתמש) PUT /users/:id: עדכון משתמש (חזיר הודעה מוצלחת) Delete /users/:id: Delete a user (חזיר הודעה של הצלחה) GET /tasks: קבל את כל המשימות (חזיר קבוצה של מזהה משימות) GET /tasks/:id: קבל משימה על ידי id (חזיר את אובייקט המשימה) POST /tasks: יצירת משימה חדשה (חוזרת את מזהה המשימה שנוצרה) PUT /tasks/:id: עדכון משימה (חזיר הודעת הצלחה) Delete /tasks/:id: למחוק משימה GET /users/:userId/tasks: קבל את כל המשימות המוענקות למשתמש (חוזרת קבוצה של מזהה המשימות) POST /users/:userId/tasks/:taskId: להקצות משימה למשתמש (חוזרת הודעת הצלחה) DELETE /users/:userId/tasks/:taskId: להסיר משימה ממשתמש (חוזר הודעה להצלחה) חזית כמו מסגרות בדרך כלל להוסיף תוספות משלהם ודרכים לעשות דברים, נשתמש Vanilla TypeScript כדי לשמור על הדברים פשוטים ו-framework-agnostic. We will create a SPA with two views: one for the user list and one for a specific user. The user list displays the user's name and the number of tasks assigned to them. Clicking on a user will take you to the user view, which shows the user's details and their tasks. And from the user view, we can go back to the user list. כדי לשמור על הדברים פשוטים, אנו נשתמש בסקרים כדי לקבל את הנתונים האחרונים מן האחורי.כל 3 שניות, אנו נעשה בקשות אל האחורי כדי לקבל את הנתונים האחרונים עבור התצוגה הנוכחית ולעדכן את UI בהתאם. עבור תצוגת רשימת המשתמשים, אנו נעשה בקשה כדי לקבל את כל מזהות המשתמש, ואז עבור כל משתמש, אנו נעשה בקשה כדי לקבל את הפרטים שלהם, ו לחשב את מספר המשימות שהוענקו להם. GET /users GET /users/:id GET /users/:id/tasks עבור תצוגת המשתמש, אנו נעשה בקשה כדי לקבל את הפרטים של המשתמש, ו כדי לקבל את מזהות המשימות שהוקצו להם, ואז עבור כל מזהה המשימות, נעשה בקשה כדי לקבל את הפרטים של המשימה. GET /users/:id GET /users/:id/tasks GET /tasks/:id תגית: GitHub Repo אתה יכול למצוא את הקוד המלא עבור דוגמה זו בהלוויה . תגית: github repo בשל כמות המקרר, פנה ל repo עבור הקוד המלא.כל שלב של המאמר יתייחס לשדה ב repo. ה-repo מכיל גם את הקוד האחורי וגם את הקוד החזיתי. הפורטל, והחזית נמצאת ב כאשר אתה מגולל את ה- repo, פעל בשני תיקיות כדי להתקין את התמכרות. לאחר מכן אתה יכול להפעיל את האחורי עם ב The הפורטוגליזציה, והחזית עם ב The הפוסט הקודם: The frontend will be served at והחזרה האחורית ב . backend frontend npm install npm run dev backend npm run dev frontend http://localhost:5173 http://localhost:3000 לאחר שעשיתם את כל המשימות ושני האחוריים והחזית שלכם פועלים, תוכלו לפתוח את הדפדפן וללכת ל: כדי לראות את האפליקציה בפעולה: http://localhost:5173 בפיתוח אם אתה ניווט , אתה צריך לראות הכל עובד בסדר גמור. אם אתה מוסיף משתמש חדש עם http://localhost:5173 curl -X POST http://localhost:3000/users \ -H "Content-Type: application/json" \ -d '{"name": "John Doe", "email": "john@example.com"}' אתה צריך לראות את המשתמש מופיע בתצוגת רשימת המשתמשים בתוך 3 שניות. להרגיש חופשי לשחק עם האפליקציה ולהוסיף יותר משתמשים ומשימות. ובכן, זה המקום שבו אנחנו סוף סוף מגיעים לנקודה של מאמר זה. backend שלנו עובד בסדר גמור. frontend שלנו, למרות boilerplate נורא, גם עובד בסדר גמור. אבל בין frontend לבין backend, יש את הרשת. ואת הרשת הוא לא אמין. אז בואו נראה מה קורה אם אנחנו להוסיף קצת flakiness backend שלנו. סימולציה של שגיאות רשת בואו להוסיף middleware ל-backend שלנו אשר נכשל באופן אקראי בקשות עם סיכוי של 20% וגם מוסיף כמה עיכוב אקראי של עד 1 שניה. אתה יכול למצוא את ה-Flash Middleware ב הנה הקוד: הנה הקוד: backend/src/middleware/flaky.ts import { Request, Response, NextFunction } from 'express'; export function flaky(req: Request, res: Response, next: NextFunction) { // Randomly fail requests with a 20% chance if (Math.random() < 0.2) { return res.status(500).json({ error: 'Random failure' }); } // Add random delay up to 2 seconds const delay = Math.random() * 2000; setTimeout(next, delay); } לאחר מכן, אנו יכולים להשתמש באמצע זה באפליקציית Express שלנו. פשוט לייבא את ה- middleware ולהשתמש בו לפני הנתיבים שלך: backend/src/index.ts ... import { flaky } from './middleware/flaky'; ... app.use(cors()); app.use(express.json()); app.use(flaky); // Use the flaky middleware קוד זה נמצא ב שדה של ה- REPO, אז אתה יכול לבדוק את זה עם . network-errors git checkout network-errors עכשיו, אם אתה מפעיל מחדש את backend שלך ולהחדיר את frontend, אתה צריך להתחיל לראות כמה דברים מוזרים. זהו הזמן שבו, אם לא כבר, אתה צריך להתחיל לחשוב על איך להתמודד עם טעויות אלה בהצלחה. undefined תסריטים שגויים קודם כל, בואו לזהות מה יכול לטעות וכיצד אנחנו יכולים להתמודד עם זה: כשלות רשת מפוקפקות: בקשות יכולות להיכשל באופן אקראי, ולכן על שגיאות מסוימות, אנו צריכים לחזור עליהן כמה פעמים לפני שנכנע. כשאנחנו מבקרים, אנחנו לא שולחים בקשה אחת בלבד, אלא בקשות מרובות באופן לא-סנכרוני. ו-3 שניות לאחר מכן, אנו שולחים קבוצה נוספת של בקשות. אם בקשה מהסדרה הקודמת עדיין מחכה כאשר הסדרה הבאה נשלחת, אנו עשויים לקבל תשובה מוקדמת לאחר אחת מאוחר יותר. זה יכול להוביל למצב UI לא עקבי. אנחנו צריכים לוודא כי רק התשובה האחרונה משמשת כדי לעדכן את ה- UI, כך שכאשר מחזור הבחירות החדש מתחיל, אנו צריכים לבטל כל בקשה מחויבת מהסדרה הקודמת. בדומה לכך, אם המשתמש ניווט אל תצוגה אחרת בזמן שביקורות מהתצוגה הקודמת עדיין ממתינות, אנו עשויים לקבל תשובות עבור התצוגה הקודמת לאחר שכבר ניווטנו. אם בקשה הייתה מוצלחת בשלב מסוים, אך לאחר מכן נכשלת במחזור הסקר הבא, איננו רוצים להציג מיד מצב שגיאה למשתמש. אנחנו צריכים להתמודד עם תסריטים שבהם, לדוגמה, אנחנו רואים משתמש שנמחק ב-backend.We need to handle 404 errors gracefully and navigate back to the user list view, or at least show a not found message. כמו כן, אנו צריכים להתמודד עם תסריטים שבהם האחורי נופל לחלוטין או בלתי נגיש. והרשימה ממשיכה, במיוחד אם ה- UI מאפשר ליצור, לעדכן או למחוק נתונים.אבל בינתיים, בואו נתמקד בפעולות הקריאה וכיצד לטפל בטעויות בעת קבלת נתונים. תגיות: Vanilla Fetch כאן, כמו עם הרבה דברים ב-JavaScript (או TypeScript), יש לך שתי אפשרויות להתמודד עם התסריטים האלה.אתה יכול לכתוב פונקציות כלי עזר משלך כדי לסובב את API Fetch ולהוסיף את ההיגיון הנדרש לניהול שגיאות, או שאתה יכול לבחור ספרייה שעושה את זה בשבילך. בואו נתחיל עם הכל בעצמנו.הקוד הוא על שדה של ה- REPO, אז אתה יכול לבדוק את זה עם . native-fetch git checkout native-fetch מה צריך לעשות מרכז את כל ההיגיון של fetch ב poller.ts. עבור כל סקר, ליצור AbortController חדש, לבטל את הקודם. Wrap fetch calls בפונקציה retry-and-timeout. בהצלחה, לעדכן קערה ולהשתמש בה כדי להציג. על כישלון, לחזור על פי הצורך, ולטפל ב-timeouts / ביטולים בהצלחה. שלנו הקובץ נראה ככה עכשיו: poller.ts // Cache for responses const cache: Record<string, any> = {}; // AbortController for cancelling requests let currentController: AbortController | undefined; // Helper: fetch with retries and timeout async function fetchWithRetry(url: string, options: RequestInit = {}, retries = 2, timeout = 3000): Promise<any> { for (let attempt = 0; attempt <= retries; attempt++) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeout); try { const res = await fetch(url, { ...options, signal: controller.signal }); clearTimeout(timer); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); return data; } catch (err) { clearTimeout(timer); if (attempt === retries) throw err; } } } // Cancel all previous requests function cancelRequests() { if (currentController) currentController.abort(); currentController = new AbortController(); } export async function fetchUserListData() { cancelRequests(); // Use cache if available if (cache.userList) return cache.userList; try { if (!currentController) throw new Error('AbortController not initialized'); const userIds = await fetchWithRetry('http://localhost:3000/users', { signal: currentController!.signal }); const users = await Promise.all(userIds.map((id: number) => fetchWithRetry(`http://localhost:3000/users/${id}`, { signal: currentController!.signal }))); const taskCounts = await Promise.all(userIds.map((id: number) => fetchWithRetry(`http://localhost:3000/users/${id}/tasks`, { signal: currentController!.signal }).then((tasks: any[]) => tasks.length))); cache.userList = { users, taskCounts }; return cache.userList; } catch (err) { // fallback to cache if available if (cache.userList) return cache.userList; throw err; } } export async function fetchUserDetailsData(userId: number) { cancelRequests(); const cacheKey = `userDetails_${userId}`; if (cache[cacheKey]) return cache[cacheKey]; try { if (!currentController) throw new Error('AbortController not initialized'); const user = await fetchWithRetry(`http://localhost:3000/users/${userId}`, { signal: currentController!.signal }); const taskIds = await fetchWithRetry(`http://localhost:3000/users/${userId}/tasks`, { signal: currentController!.signal }); const tasks = await Promise.all(taskIds.map((id: number) => fetchWithRetry(`http://localhost:3000/tasks/${id}`, { signal: currentController!.signal }))); cache[cacheKey] = { user, tasks }; return cache[cacheKey]; } catch (err) { if (cache[cacheKey]) return cache[cacheKey]; throw err; } } נמחקנו כל ההיגיון האנושי נמצא כעת במקרה המשתמש הפשוט שלנו, זה סובלני, אבל אפילו כאן, היינו צריכים להוסיף הרבה קוד boilerplate כדי לטפל בטעויות, retries, timeouts, ביטולים, ו- caching. api.ts poller.ts אם אתה מפעיל את האפליקציה עכשיו, אתה צריך לראות כי זה עובד הרבה יותר טוב. ה- UI הוא יציב יותר ולא מתפרק לעתים קרובות. היתרונות של גישה זו עוד קוד קוטל: היינו צריכים לכתוב הרבה קוד כדי להתמודד עם שגיאות, retries, timeouts, ביטולים, וחיווט. לא בשימוש חוזר: הקוד קשור באופן הדוק למקרה השימוש הספציפי שלנו ולא בשימוש חוזר במיוחד עבור פרויקטים או תסריטים אחרים. תכונות מוגבלות: הקוד מתמודד רק עם תסריטי שגיאה בסיסיים.תסריטים מורכבים יותר כגון Backoff אקספוננציונלי, מפסיקי מעגלים, או ניהול שגיאות גלובלי ידרשו אפילו יותר קוד. שימוש תגית: better fetch handling FETCH FETCH כדי לפתור את החסרונות של הניהול המותאם אישית שלנו, כתבתי ספרייה בשם זוהי ספריה קטנה וקלה המקיפה את API Fetch ומספקת דרך פשוטה ומפורשת להתמודד עם שגיאות, retries, timeouts, ביטולים וכמה תכונות נוספות. ffetch בואו נכתוב מחדש את ההיגיון שלנו באמצעות אתה יכול למצוא את הקוד על שדה של ה- REPO, אז אתה יכול לבדוק את זה עם . ffetch ffetch git checkout ffetch קודם כל, להתקין ב The הפורטל : ffetch frontend npm install @gkoos/ffetch לאחר מכן נוכל לכתוב מחדש את קובץ שימוש : poller.ts ffetch import createClient from '@gkoos/ffetch'; // Cache for responses const cache: Record<string, any> = {}; // Create ffetch client const api = createClient({ timeout: 3000, retries: 2, }); function cancelRequests() { api.abortAll(); } export async function fetchUserListData() { cancelRequests(); if (cache.userList) return cache.userList; try { const userIds = await api('http://localhost:3000/users').then(r => r.json()); const users = await Promise.all( userIds.map((id: number) => api(`http://localhost:3000/users/${id}`).then(r => r.json())) ); const taskCounts = await Promise.all( userIds.map((id: number) => api(`http://localhost:3000/users/${id}/tasks`).then(r => r.json()).then((tasks: any[]) => tasks.length)) ); cache.userList = { users, taskCounts }; return cache.userList; } catch (err) { if (cache.userList) return cache.userList; throw err; } } export async function fetchUserDetailsData(userId: number) { cancelRequests(); const cacheKey = `userDetails_${userId}`; if (cache[cacheKey]) return cache[cacheKey]; try { const user = await api(`http://localhost:3000/users/${userId}`).then(r => r.json()); const taskIds = await api(`http://localhost:3000/users/${userId}/tasks`).then(r => r.json()); const tasks = await Promise.all( taskIds.map((id: number) => api(`http://localhost:3000/tasks/${id}`).then(r => r.json())) ); cache[cacheKey] = { user, tasks }; return cache[cacheKey]; } catch (err) { if (cache[cacheKey]) return cache[cacheKey]; throw err; } } הקוד נקי יותר וקל יותר לקריאה, ואיננו צריכים לדאוג לשחזרים, לשעות או לבטלות יותר. אנחנו פשוט יוצרים לקוח עם האפשרויות הרצויות ומשתמשים בו כדי לבצע בקשות. ffetch יתרונות נוספים של השימוש ffetch מחסום מעגל: חימום אוטומטי של נקודת הקצה לאחר כישלונות חוזרים חיסכון אקספונציונלי אוטומטי עבור מחזורים: משך זמן ההמתנה בין מחזורים ניהול שגיאות גלובלי: חרוזים לרישום, שינוי בקשות / תשובות וכו ' לדוגמה, אנו יכולים לבחור לשחזר על שגיאות רשת ושגיאות שרת 5xx, אבל לא על שגיאות הלקוח 4xx. זה לא עושה שום דבר קסום שאתה לא יכול לבנות בעצמך, אבל זה חוסך אותך מלכתוב, לבדוק, ולשמור על כל המקרר הזה.זה מכסה נוחות כי חבילות דפוסים ברמה הייצור (כגון מחסום מעגלים ו Backoff) כך שאתה יכול להתמקד באפליקציה שלך, לא ההיגיון של האספקה שלך.זה גם עוצר על שכבת האספקה, כך שאתה עדיין יכול להשתמש הקאשינג שלך, ניהול המדינה, וספריות UI כפי שאתה רואה מתאים. ffetch מסקנה התוצאה העיקרית של מאמר זה היא לא שאתה צריך להשתמש באופן ספציפי, אבל אתה לא צריך לסמוך על vanilla Fetch עבור יישומים מוכנים לייצור. הרשת היא לא אמינה, ואתה צריך להיות מוכן לזה. ffetch מה בדיוק אתה צריך לעשות תלוי במקרה השימוש הספציפי שלך ואת הדרישות, אבל אתה לא יכול ללכת לייצור להתמודד עם הדרך המאושרת בלבד. אפשר לעזור עם זה. ffetch