JavaScript · Синтаксис · Промежуточный
Кастомные ошибки и обработка ошибок
Встроенные типы ошибок, пользовательские подклассы Error и структурированные паттерны обработки ошибок.
Краткое введение в тему и пояснения перед упражнениями (упражнения ниже):
try/catch/finally, перебрасывание и необработанные отклонения промисов
#Стратегии обработки ошибок: throw vs паттерн Result, Error.cause
#Упражнения:
Напишите функцию, которая безопасно делит два числа. Если делитель равен 0, бросайте `RangeError` с сообщением 'Division by zero'.
function safeDivide(a, b) {
// ваш код здесь
}
console.log(safeDivide(10, 2)); // 5
try {
safeDivide(10, 0);
} catch (e) {
console.log(e instanceof RangeError); // true
console.log(e.message); // 'Division by zero'
}
Решение
function safeDivide(a, b) {
if (b === 0) throw new RangeError('Division by zero');
return a / b;
}
Создайте пользовательский класс ошибки `ValidationError`, расширяющий `Error`. Он должен принимать имя поля и сообщение, сохраняя их как свойства.
class ValidationError extends Error {
constructor(field, message) {
// ваш код здесь
}
}
const e = new ValidationError('email', 'Invalid format');
console.log(e instanceof ValidationError); // true
console.log(e instanceof Error); // true
console.log(e.field); // 'email'
console.log(e.message); // 'Invalid format'
console.log(e.name); // 'ValidationError'
Решение
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
Напишите блок `try/catch/finally`, разбирающий строку JSON. Если разбор не удаётся, верните значение по умолчанию `{}`. Блок `finally` должен логировать 'parse attempted'.
function safeParse(str) {
// ваш код здесь
}
console.log(safeParse('{"a":1}')); // { a: 1 }
console.log(safeParse('invalid')); // {}
// 'parse attempted' должно логироваться в обоих случаях
Решение
function safeParse(str) {
try {
return JSON.parse(str);
} catch (e) {
return {};
} finally {
console.log('parse attempted');
}
}
Напишите функцию, принимающую массив и индекс. Если индекс выходит за границы, бросайте `RangeError`. Если аргумент не является массивом, бросайте `TypeError`.
function getElement(arr, index) {
// ваш код здесь
}
console.log(getElement([1, 2, 3], 1)); // 2
try { getElement('not array', 0); } catch(e) { console.log(e.constructor.name); } // TypeError
try { getElement([1,2], 5); } catch(e) { console.log(e.constructor.name); } // RangeError
Решение
function getElement(arr, index) {
if (!Array.isArray(arr)) throw new TypeError('Expected an array');
if (index < 0 || index >= arr.length) throw new RangeError(`Index ${index} out of bounds`);
return arr[index];
}
Напишите функцию, перехватывающую специфический тип ошибки и повторно бросающую любые другие ошибки, которые она не знает как обрабатывать.
class NetworkError extends Error {
constructor(msg) { super(msg); this.name = 'NetworkError'; }
}
function handleRequest(fn) {
// Вызовите fn(). Если бросает NetworkError, верните 'network error handled'.
// Если бросает что-то другое, повторно бросайте.
}
console.log(handleRequest(() => { throw new NetworkError('timeout'); }));
// 'network error handled'
try {
handleRequest(() => { throw new TypeError('bad type'); });
} catch (e) {
console.log(e instanceof TypeError); // true
}
Решение
class NetworkError extends Error {
constructor(msg) { super(msg); this.name = 'NetworkError'; }
}
function handleRequest(fn) {
try {
return fn();
} catch (e) {
if (e instanceof NetworkError) return 'network error handled';
throw e;
}
}
Обработайте асинхронные ошибки: напишите асинхронную функцию, загружающую URL (симулированный). Если загрузка не удаётся, логируйте сообщение об ошибке и верните null.
async function simulatedFetch(url) {
if (!url.startsWith('https')) throw new Error(`Invalid URL: ${url}`);
return { status: 200, data: 'ok' };
}
async function safeFetch(url) {
// ваш код здесь
}
safeFetch('https://example.com').then(console.log); // { status: 200, data: 'ok' }
safeFetch('http://bad.com').then(console.log); // null (и логирует ошибку)
Решение
async function safeFetch(url) {
try {
return await simulatedFetch(url);
} catch (e) {
console.error(e.message);
return null;
}
}
Напишите функцию, оборачивающую другую функцию и перехватывающую любой TypeError, возвращая вместо него значение по умолчанию. Другие типы ошибок должны распространяться нормально.
function withTypeErrorFallback(fn, defaultValue) {
// ваш код здесь
}
const safe = withTypeErrorFallback(() => null.length, 0);
console.log(safe); // 0
try {
withTypeErrorFallback(() => { throw new RangeError('oops'); }, 0);
} catch (e) {
console.log(e instanceof RangeError); // true
}
Решение
function withTypeErrorFallback(fn, defaultValue) {
try {
return fn();
} catch (e) {
if (e instanceof TypeError) return defaultValue;
throw e;
}
}
Создайте класс `ErrorRegistry`, записывающий все ошибки, брошенные во время пакетной обработки. Он должен их собирать, а не останавливаться на первой.
class ErrorRegistry {
constructor() {
this.errors = [];
}
run(fn) {
// Вызовите fn(). Если бросает, записывайте ошибку. Никогда не перебрасывайте.
}
hasErrors() { return this.errors.length > 0; }
getErrors() { return this.errors; }
}
const reg = new ErrorRegistry();
reg.run(() => { /* ok */ });
reg.run(() => { throw new Error('first'); });
reg.run(() => { throw new TypeError('second'); });
console.log(reg.hasErrors()); // true
console.log(reg.getErrors().length); // 2
Решение
class ErrorRegistry {
constructor() {
this.errors = [];
}
run(fn) {
try {
fn();
} catch (e) {
this.errors.push(e);
}
}
hasErrors() { return this.errors.length > 0; }
getErrors() { return this.errors; }
}
Реализуйте цепочку ошибок: создайте функцию, оборачивающую низкоуровневую ошибку в высокоуровневую, сохраняя исходную ошибку как `cause`.
function readConfig(filename) {
throw new Error(`File not found: ${filename}`);
}
function loadApp(configPath) {
// Попробуйте readConfig(configPath). Если не удаётся, бросайте новый Error
// 'Failed to load application' с оригиналом как 'cause'
}
try {
loadApp('config.json');
} catch (e) {
console.log(e.message); // 'Failed to load application'
console.log(e.cause.message); // 'File not found: config.json'
}
Решение
function readConfig(filename) {
throw new Error(`File not found: ${filename}`);
}
function loadApp(configPath) {
try {
return readConfig(configPath);
} catch (e) {
throw new Error('Failed to load application', { cause: e });
}
}
Напишите функцию `assertType`, бросающую `TypeError` с описательным сообщением, если значение не является ожидаемого типа (проверяется через `typeof`).
function assertType(value, expectedType) {
// ваш код здесь
}
assertType(42, 'number'); // ok, не бросает
assertType('hello', 'string'); // ok
try {
assertType(42, 'string');
} catch (e) {
console.log(e instanceof TypeError); // true
console.log(e.message); // например 'Expected string, got number'
}
Решение
function assertType(value, expectedType) {
const actual = typeof value;
if (actual !== expectedType) {
throw new TypeError(`Expected ${expectedType}, got ${actual}`);
}
}