Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Ошибки в асинхронном коде

Как работают 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
}

Так задачи выполняются по очереди, а любая ошибка всплывает туда, где её можно перехватить.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.