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}`);
}
}