Các tuyến bất đồng bộ
Thuộc phần Kiến thức cơ bản về Express trong hành trình Node.js của Coddy. Bài học 21 trên 23.
Mảng sẽ biến mất khi máy chủ khởi động lại. Tệp sẽ lưu giữ dữ liệu.
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));
});Một route có thể là hàm async chờ bằng await, trong trường hợp này là chờ readFile từ node:fs/promises. Express 5 theo dõi promise mà route trả về: nếu promise bị từ chối vì tệp không tồn tại hoặc JSON bị lỗi, lỗi sẽ được chuyển đến trình xử lý lỗi của bạn, giống như khi có lệnh throw.
Nếu không có await, bạn sẽ giữ một promise thay vì dữ liệu, và res.json(promise) sẽ gửi {}. Tệp data/recipes.json mới chứa bốn công thức.
Thử thách
DễMảng trong bộ nhớ sẽ mất mỗi khi máy chủ khởi động lại. Thay vào đó, hãy lưu các công thức trong tệp data/recipes.json, tệp này đã chứa bốn công thức.
- Trong
routes/recipes.js, nhậpreadFilevàwriteFiletừnode:fs/promises, đồng thời ngừng nhậprecipestừ../data.js. - Viết một hàm trợ giúp duy nhất,
loadRecipes(), là hàm bất đồng bộ đọcdata/recipes.jsonvà trả về danh sách đã phân tích cú pháp. Các bài học tiếp theo sẽ dùng tên này. - Đặt mọi tuyến trong tệp thành
async, và bắt đầu mỗi tuyến bằng cách chờloadRecipes():/tags, danh sách, một công thức, tuyến POST và tuyến scale. - Trong tuyến POST, sau khi thêm công thức mới vào danh sách, hãy ghi toàn bộ danh sách trở lại
data/recipes.jsonbằngwriteFile, dưới dạng văn bản JSON. - Tuyến scale vẫn ném lỗi nếu công thức không có số khẩu phần, giờ đây là từ bên trong một hàm bất đồng bộ. Express 5 vẫn chuyển lỗi đó đến trình xử lý lỗi như trước, nên máy chủ vẫn phản hồi với mã 500 và
{"error": "Something went wrong"}. Đừng bắt lỗi trong tuyến.
Thử nhé: Nhấn Chạy và gửi /api/recipes: vẫn là bốn công thức đó, giờ được đọc từ tệp. Chọn POST và gửi {"title": "Gazpacho", "tags": ["vegan", "soup"]} đến /api/recipes: mã trạng thái 201 và công thức có "id": 5. Gửi /api/recipes/5: Gazpacho. Gửi /api/recipes/99: mã trạng thái 404. Sau đó POST {"title": "Toast"}, công thức này không có số khẩu phần và trở thành công thức số 6, rồi gửi /api/recipes/6/scale?by=2: mã trạng thái 500 và {"error":"Something went wrong"}. Nếu bạn thấy {} thay vì dữ liệu, tức là còn thiếu await.
Tự làm thử
import express from 'express';
import { recipes } from '../data.js';
const router = express.Router();
// Mỗi thẻ xuất hiện một lần, theo thứ tự bảng chữ cái.
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;
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Kiến thức cơ bản về Express
Tự luyện tập: Playground Express