المسارات غير المتزامنة
جزء من قسم أساسيات Express في رحلة Node.js على Coddy. الدرس 21 من 23.
تختفي المصفوفة عند إعادة تشغيل الخادم. أما الملف فيحتفظ بالبيانات.
import { readFile } from 'node:fs/promises';
app.get('/api/forecast', async (req, res) => {
const text = await readFile('data/forecast.json', 'utf8');
res.json(JSON.parse(text));
});يمكن أن يكون المسار دالة async تنتظر باستخدام await، وهنا تنتظر readFile من node:fs/promises. يراقب Express 5 الوعد الذي تعيده الدالة: إذا رُفض، بسبب فقدان الملف أو وجود خلل في JSON الخاص به، يصل الخطأ إلى معالج الأخطاء لديك، كما يحدث عند رمي استثناء.
من دون await، تحتفظ بوعد بدلًا من البيانات، وres.json(promise) يرسل {}. يحتوي الملف الجديد data/recipes.json على الوصفات الأربع.
التحدي
سهلتُفقد المصفوفة الموجودة في الذاكرة كلما أُعيد تشغيل الخادم. احتفظ بالوصفات في ملف بدلًا من ذلك: data/recipes.json، الذي يحتوي بالفعل على الوصفات الأربع.
- في
routes/recipes.js، استوردreadFileوwriteFileمنnode:fs/promises، وتوقف عن استيرادrecipesمن../data.js. - اكتب دالة مساعدة واحدة،
loadRecipes()، وهي دالة غير متزامنة تقرأdata/recipes.jsonوتُرجع القائمة بعد تحليلها. ستستخدمها الدروس التالية بهذا الاسم. - اجعل كل مسار في الملف
async، وابدأ كل واحد منها بانتظارloadRecipes():/tags، والقائمة، ووصفة واحدة، ومسار POST، ومسار التحجيم. - في مسار POST، بعد إضافة الوصفة الجديدة إلى القائمة، اكتب القائمة كاملةً مرة أخرى إلى
data/recipes.jsonباستخدامwriteFile، كنص JSON. - يستمر مسار التحجيم في إطلاق خطأ عند غياب عدد الحصص عن وصفة، وهذه المرة من داخل دالة غير متزامنة. يمرر Express 5 هذا الخطأ إلى معالج الأخطاء كما سبق، لذا ستظل الاستجابة برمز 500 و
{"error": "Something went wrong"}. لا تعالج الخطأ داخل المسار.
جرّب ذلك: اضغط على «العب» وأرسل /api/recipes: ستظهر الوصفات الأربع نفسها، لكن هذه المرة مقروءة من الملف. اختر POST وأرسل {"title": "Gazpacho", "tags": ["vegan", "soup"]} إلى /api/recipes: ستكون الحالة 201 وستظهر الوصفة مع "id": 5. أرسل /api/recipes/5: ستظهر Gazpacho. أرسل /api/recipes/99: ستكون الحالة 404. ثم أرسل POST مع {"title": "Toast"}، وهي وصفة لا تحتوي على عدد حصص وتصبح الوصفة 6، ثم أرسل /api/recipes/6/scale?by=2: ستكون الحالة 500 والاستجابة {"error":"Something went wrong"}. إذا رأيت {} بدلًا من بياناتك، فهذا يعني أن هناك await مفقودًا.
جرّب بنفسك
import express from 'express';
import { recipes } from '../data.js';
const router = express.Router();
// كل وسم مرة واحدة، مرتبةً أبجديًا.
router.get('/tags', (req, res) => {
const tags = new Set(recipes.flatMap((recipe) => recipe.tags));
res.json([...tags].sort());
});
router.get('/', (req, res) => {
res.json(recipes);
});
router.get('/: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);
});
router.post('/', (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);
});
router.get('/:id/scale', (req, res) => {
const recipe = recipes.find((r) => r.id === Number(req.params.id));
if (!recipe) {
res.status(404).json({ error: 'No such recipe' });
return;
}
const by = Number(req.query.by);
if (!Number.isFinite(by) || by <= 0) {
res.status(400).json({ error: 'Scale by a positive number' });
return;
}
if (!recipe.servings) {
throw new Error(`Recipe ${recipe.id} has no servings`);
}
res.json({ title: recipe.title, servings: recipe.servings * by });
});
export default router;
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس أساسيات Express
تدرّب بنفسك: Playground لـ Express