Ошибки в асинхронном коде
Как работают try...catch и промисы, вы знаете из учебника. В Node.js у ошибок в асинхронном коде есть важное последствие: промис, который завершился ошибкой и которую никто не перехватил, — необработанный отказ (unhandled rejection) — останавливает всю программу. Для сервера это значит, что сайт перестаёт открываться у всех.
try...catch вокруг await
Ошибку await перехватывают обычным try...catch:
import { readFile } from 'node:fs/promises';
async function loadPosts() {
try {
const text = await readFile('posts.json', 'utf8');
return JSON.parse(text);
} catch (error) {
console.log(`Не удалось загрузить статьи: ${error.code}`);
return [];
}
}
console.log(await loadPosts());Файла нет, поэтому выведется «Не удалось загрузить статьи: ENOENT» и пустой массив.
Забытый await
try...catch ловит ошибку промиса только тогда, когда промис ожидают через await внутри блока try:
// app.js
import { readFile } from 'node:fs/promises';
try {
readFile('posts.json', 'utf8'); // await забыт
} catch {
console.log('Сюда мы не попадём');
}Блок catch не сработает: промис отклонится позже, когда try уже закончился. Node.js напечатает ошибку ENOENT и завершит программу с кодом 1.
Ошибки в обратных вызовах
В функциях, которые вызывает таймер или событие, внешний try...catch тоже бессилен — к моменту вызова он уже отработал. Ловите ошибку внутри самой функции:
setTimeout(() => {
try {
JSON.parse('{ сломанный json');
} catch (error) {
console.log(`Ошибка в таймере: ${error.name}`);
}
}, 10);Выведется «Ошибка в таймере: SyntaxError».
Последний рубеж
Событие unhandledRejection объекта process срабатывает для отказа, который никто не обработал. В нём ошибку записывают в журнал и завершают программу с кодом ошибки:
// src/server.js
process.on('unhandledRejection', (reason) => {
console.error('Необработанная ошибка:', reason);
process.exit(1);
});Это не способ «замолчать» ошибку, а запасной вариант, чтобы она точно попала в журнал. Сам сервер после этого перезапускают — как это делает менеджер процессов, рассказано в уроке про развёртывание. Похожее событие для синхронных ошибок — uncaughtException.
Ошибки в параллельных задачах
Promise.all() отклоняется при первой ошибке. Если нужны результаты всех задач, используйте Promise.allSettled():
const results = await Promise.allSettled([
Promise.resolve('статья 1'),
Promise.reject(new Error('статья 2 не найдена')),
]);
for (const result of results) {
console.log(result.status === 'fulfilled' ? result.value : result.reason.message);
}Выведется «статья 1» и «статья 2 не найдена».
Частая ошибка
Внутри forEach с асинхронной функцией ошибки и await теряются: forEach не ждёт промисы. Пишите цикл for...of:
// app.js
for (const id of [1, 2, 3]) {
await publishPost(id); // ошибка попадёт во внешний try...catch
}Так задачи выполняются по очереди, а любая ошибка всплывает туда, где её можно перехватить.