Як обробляти async/await в обробниках маршрутів Express.js?
Async/Await в Express.js
Express був спроектований до того, як з'явилися async/await. За замовчуванням, Express не перехоплює необроблені відхилення Promise з асинхронних обробників маршрутів — вони тихо зривають ваш додаток або викликають попередження про необроблені відхилення.
Проблема
// ❌ ЛОМАНИЙ — необроблене відхилення Promise!
app.get('/users', async (req, res) => {
const users = await User.findAll(); // викидає? Express цього не перехоплює!
res.json(users);
});Якщо User.findAll() відхиляється, помилка не передається обробнику помилок Express.
Рішення 1: try/catch + next(err)
// ✅ Правильно — але громіздко
app.get('/users', async (req, res, next) => {
try {
const users = await User.findAll();
res.json(users);
} catch (err) {
next(err); // передати обробнику помилок
}
});Це працює, але написання try/catch в кожному обробнику є нудним.
Рішення 2: обгортка asyncHandler
// utils/asyncHandler.js
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
module.exports = asyncHandler;
// Чисті обробники маршрутів!
const asyncHandler = require('../utils/asyncHandler');
app.get('/users', asyncHandler(async (req, res) => {
const users = await User.findAll();
res.json(users);
}));
app.post('/users', asyncHandler(async (req, res) => {
const user = await User.create(req.body);
res.status(201).json(user);
}));Рішення 3: express-async-errors (Monkey-Patch)
npm install express-async-errors// Імпортуйте ОДИН раз на початку app.js
require('express-async-errors');
// Тепер асинхронні помилки автоматично передаються в next()!
app.get('/users', async (req, res) => {
const users = await User.findAll(); // якщо викидає → йде до обробника помилок
res.json(users);
});Це внутрішньо патчує Express. Обгортка не потрібна.
Рішення 4: Express 5 (Автоматично)
Express 5 (в даний час RC) обробляє асинхронні помилки нативно:
npm install express@next// Express 5 — асинхронні помилки автоматично передаються в next()
app.get('/users', async (req, res) => {
const users = await User.findAll();
res.json(users);
});Паралельні асинхронні операції
app.get('/dashboard', asyncHandler(async (req, res) => {
// ❌ Послідовно — повільно! (кожен чекає попередній)
const users = await fetchUsers();
const posts = await fetchPosts();
const stats = await fetchStats();
// ✅ Паралельно — всі виконуються одночасно
const [users, posts, stats] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchStats()
]);
res.json({ users, posts, stats });
}));Обробка тайм-аутів
function withTimeout(promise, ms = 5000) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Запит перевищив час очікування після ${ms}ms`)), ms)
);
return Promise.race([promise, timeout]);
}
app.get('/slow', asyncHandler(async (req, res) => {
const data = await withTimeout(slowExternalAPI(), 3000);
res.json(data);
}));Найкращі практики
- Завжди використовуйте обгортку
asyncHandlerАБОexpress-async-errors - Ніколи не приховуйте помилки — завжди викликайте
next(err)або дозвольте обгортці зробити це - Використовуйте
Promise.all()для незалежних асинхронних операцій (швидше) - Встановлюйте тайм-аути для викликів зовнішніх API
- Використовуйте Express 5 для нових проектів — нативна підтримка асинхронності
Резюме
Express не обробляє асинхронні помилки автоматично. Використовуйте обгортку asyncHandler, express-async-errors або переходьте на Express 5, щоб безпечно використовувати async/await у ваших обробниках маршрутів. Завжди передавайте помилки в next(err), щоб ваш центральний обробник помилок міг їх обробити.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.