טיפול בשגיאות
חלק מהיחידה יסודות Express במסלול ה-Node.js של Coddy. שיעור 18 מתוך 23.
כשנתיב גורם לשגיאה, Express משיב עם דף השגיאה שלו, שאינו JSON ומציג פרטים שמיועדים לך.
app.get('/api/fines/:member', (req, res) => {
throw new Error('Fines database is down');
});
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Try again later' });
});תוכנת ביניים עם ארבעה ארגומנטים, (err, req, res, next), היא מטפל בשגיאות. Express סופרת את הארגומנטים: עם שלושה, מדובר בתוכנת ביניים רגילה, והשגיאות מדלגות עליה. כשנתיב גורם לשגיאה, Express מדלגת על תוכנת הביניים הרגילה וקוראת למטפל השגיאות עם השגיאה. הוסף אותו בסוף, אחרי מטפל 404. רשום את הפרטים ביומן לשימושך; שלח ללקוח הודעה קצרה.
throw new Error('text') עוצר את הנתיב ומעביר את השגיאה ל-Express, וגם הקריאה ל-next(err) עושה זאת. השב עם 400 כשהמבקר שלח משהו שגוי ויכול לתקן אותו. זרוק שגיאה כשהשרת אשם.
אתגר
קלממשיכים עם Recipe Box. אפשר להתעלם מקובצי practice-errors/ הנפרדים; לחיצה על הפעלה מפעילה עכשיו את server.js של הפרויקט.
טבחים רוצים להתאים את הכמויות במתכון, למשל להכין פי שניים מנות למסיבה. אי אפשר להתאים את הכמויות במתכון שנשמר בלי מספר מנות. זו תקלה בנתונים שלך, ולא משהו שהמבקר יכול לתקן, ולכן היא מועברת למטפל שגיאות שמחזיר תשובת JSON נקייה עם סטטוס 500.
- ב-
server.js, מעל מטפל השגיאות של 404, הוסיפו נתיב עבור/api/recipes/:id/scale. הנתיב קורא את המקדם ממחרוזת השאילתה, כמו ב-/api/recipes/1/scale?by=2. - אם אין מתכון עם המזהה הזה, החזירו תשובה עם סטטוס 404 ו-
{"error": "No such recipe"}. - המירו את
byלמספר. אם הוא אינו מספר, או שהוא 0 או פחות, החזירו תשובה עם סטטוס 400 ו-{"error": "Scale by a positive number"}.Number('lots')הואNaN, ו-Number.isNaNמזהה אותו. - אם אין למתכון
servings, זרקוErrorחדש שההודעה שלו היאRecipe 5 has no servings, כאשר 5 מוחלף במזהה של אותו מתכון. - אחרת, החזירו JSON שמכיל רק את הכותרת ואת מספר המנות לאחר התאמת הכמויות. במתכון 1, Tomato Soup, יש 4 מנות, ולכן
/api/recipes/1/scale?by=2מחזיר{"title": "Tomato Soup", "servings": 8}. אל תוסיפו שדות אחרים. - בסוף רשימת הביניים של הקובץ, אחרי מטפל השגיאות של 404, הוסיפו מטפל שגיאות: קריאה ל-
app.useשהפונקציה שלה מקבלת ארבעה ארגומנטים,(err, req, res, next). היא מדפיסה את השגיאה באמצעותconsole.errorומחזירה תשובה עם סטטוס 500 ו-{"error": "Something went wrong"}.
נסו זאת: לחצו על הפעלה ושלחו /api/recipes/1/scale?by=2. אתם אמורים לראות {"title":"Tomato Soup","servings":8}. שלחו /api/recipes/2/scale?by=lots: סטטוס 400 ו-{"error":"Scale by a positive number"}. שלחו /api/recipes/99/scale?by=2: סטטוס 404. עכשיו בחרו POST ושלחו {"title": "Toast"} אל /api/recipes. הוא הופך למתכון 5, ללא מספר מנות. שלחו /api/recipes/5/scale?by=2: סטטוס 500 ו-{"error":"Something went wrong"}, ובמסוף תופיע השגיאה Recipe 5 has no servings.
נסו בעצמכם
import { escapeHtml } from './html.js';
import express from 'express';
import { randomUUID } from 'node:crypto';
import { recipes } from './data.js';
const app = express();
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
app.use((req, res, next) => {
res.set('X-Request-Id', randomUUID());
next();
});
app.use(express.json());
app.use(express.static('public'));
app.get('/', (req, res) => {
const items = recipes.map((r) => `<li><a href="/recipes/${r.id}">${escapeHtml(r.title)}</a></li>`);
res.send(`<link rel="stylesheet" href="/style.css"><h1>Recipe Box</h1><ul>${items.join('')}</ul>`);
});
// type('text') שולחת טקסט פשוט, כך שכל כותרת מופיעה בשורה נפרדת.
app.get('/recipes', (req, res) => {
const { tag } = req.query;
const list = tag ? recipes.filter((recipe) => recipe.tags.includes(tag)) : recipes;
res.type('text').send(list.map((recipe) => recipe.title).join('\n'));
});
app.get('/recipes/:id', (req, res) => {
const recipe = recipes.find((r) => r.id === Number(req.params.id));
if (!recipe) {
res.status(404).send('No such recipe');
return;
}
res.send(recipe.title);
});
app.get('/api/recipes', (req, res) => {
res.json(recipes);
});
app.get('/api/recipes/:id', (req, res) => {
const recipe = recipes.find((r) => r.id === Number(req.params.id));
if (!recipe) {
res.status(404).json({ error: 'No such recipe' });
return;
}
res.json(recipe);
});
app.post('/api/recipes', (req, res) => {
const { title, tags } = req.body;
if (!title) {
res.status(400).json({ error: 'A recipe needs a title' });
return;
}
const recipe = { id: recipes.length + 1, title, tags: tags ?? [] };
recipes.push(recipe);
res.status(201).json(recipe);
});
app.get('/about', (req, res) => {
res.send('Recipe Box collects family recipes.');
});
app.get('/menu', (req, res) => {
res.redirect(301, '/recipes');
});
app.get('/menu/:id', (req, res) => {
res.redirect(301, `/recipes/${req.params.id}`);
});
app.use((req, res) => {
if (req.path.startsWith('/api/')) {
res.status(404).json({ error: 'Not found' });
return;
}
res.status(404).send('<h1>Page not found</h1><p><a href="/">Back to Recipe Box</a></p>');
});
app.listen(3000, () => {
console.log('Recipe Box is running on port 3000');
});
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה יסודות Express
תרגלו בעצמכם: Express אונליין