Що таке throw new TypeError і де це використовувати?
TypeError - це один з найпоширеніших типів помилок у JavaScript, який виникає при спробі використати значення неправильного типу. Розуміння того, коли та як використовувати throw new TypeError, є критично важливим для написання надійного коду.
Що таке TypeError?
TypeError - це вбудований клас помилок JavaScript, який успадковується від базового класу Error. Він представляє помилки, які виникають при неправильному використанні типів даних.
// Приклад автоматичного TypeError
const number = 42;
number.toUpperCase(); // TypeError: number.toUpperCase is not a function
// Ручне створення TypeError
throw new TypeError("Очікувався рядок, отримано число");
Коли JavaScript автоматично кидає TypeError
1. Виклик не-функції як функції
const notAFunction = "Це рядок";
notAFunction(); // TypeError: notAFunction is not a function
const obj = { name: "Іван" };
obj.nonExistentMethod(); // TypeError: obj.nonExistentMethod is not a function
2. Доступ до властивостей null або undefined
const nullValue = null;
console.log(nullValue.length); // TypeError: Cannot read properties of null
const undefinedValue = undefined;
undefinedValue.toString(); // TypeError: Cannot read properties of undefined
3. Неправильне використання операторів
const str = "текст";
str++; // TypeError: Cannot convert a BigInt value to a number (у деяких версіях)
// Спроба змінити readonly властивість в strict mode
"use strict";
const obj = {};
Object.defineProperty(obj, "readOnly", {
value: 42,
writable: false
});
obj.readOnly = 100; // TypeError: Cannot assign to read only property
Коли створювати TypeError власноруч
1. Валідація типів параметрів функції
function calculateArea(width, height) {
// Перевірка типів вхідних параметрів
if (typeof width !== 'number') {
throw new TypeError(`Ширина повинна бути числом, отримано ${typeof width}`);
}
if (typeof height !== 'number') {
throw new TypeError(`Висота повинна бути числом, отримано ${typeof height}`);
}
if (width <= 0 || height <= 0) {
throw new TypeError('Ширина та висота повинні бути додатними числами');
}
return width * height;
}
// Правильне використання
console.log(calculateArea(5, 10)); // 50
// Неправильне використання - кидає TypeError
try {
calculateArea("5", 10);
} catch (error) {
console.error(error.message); // Ширина повинна бути числом, отримано string
}
2. Валідація структури об'єктів
function processUser(user) {
// Перевірка, що параметр є об'єктом
if (typeof user !== 'object' || user === null) {
throw new TypeError('Параметр user повинен бути об\'єктом');
}
// Перевірка наявності обов'язкових властивостей
if (typeof user.name !== 'string') {
throw new TypeError('Властивість name повинна бути рядком');
}
if (typeof user.age !== 'number' || user.age < 0) {
throw new TypeError('Властивість age повинна бути додатним числом');
}
return `Користувач ${user.name}, вік: ${user.age}`;
}
// Приклади використання
const validUser = { name: "Олена", age: 25 };
console.log(processUser(validUser)); // Користувач Олена, вік: 25
try {
processUser({ name: "Іван" }); // Відсутня властивість age
} catch (error) {
console.error(error.message); // Властивість age повинна бути додатним числом
}
3. Валідація масивів та їх елементів
function processNumbers(numbers) {
if (!Array.isArray(numbers)) {
throw new TypeError('Параметр повинен бути масивом');
}
if (numbers.length === 0) {
throw new TypeError('Масив не може бути пустим');
}
// Перевірка кожного елемента
for (let i = 0; i < numbers.length; i++) {
if (typeof numbers[i] !== 'number') {
throw new TypeError(`Елемент з індексом ${i} повинен бути числом, отримано ${typeof numbers[i]}`);
}
if (!Number.isFinite(numbers[i])) {
throw new TypeError(`Елемент з індексом ${i} повинен бути скінченним числом`);
}
}
return numbers.reduce((sum, num) => sum + num, 0);
}
// Правильне використання
console.log(processNumbers([1, 2, 3, 4, 5])); // 15
// Помилки
try {
processNumbers([1, "2", 3]); // TypeError: Елемент з індексом 1 повинен бути числом
} catch (error) {
console.error(error.message);
}
Сучасні підходи до валідації типів
1. Використання з деструктуризацією
function createUser({ name, email, age } = {}) {
// Валідація деструктурованих параметрів
if (typeof name !== 'string' || name.trim() === '') {
throw new TypeError('Ім\'я повинно бути непустим рядком');
}
if (typeof email !== 'string' || !email.includes('@')) {
throw new TypeError('Email повинен бути валідним рядком з символом @');
}
if (typeof age !== 'number' || age < 0 || age > 150) {
throw new TypeError('Вік повинен бути числом від 0 до 150');
}
return { name: name.trim(), email: email.toLowerCase(), age };
}
// Використання
try {
const user = createUser({
name: " Марія ",
email: "MARIA@EXAMPLE.COM",
age: 28
});
console.log(user); // { name: "Марія", email: "maria@example.com", age: 28 }
} catch (error) {
console.error(error.message);
}
2. Валідація з використанням rest параметрів
function multiply(multiplier, ...numbers) {
if (typeof multiplier !== 'number') {
throw new TypeError('Множник повинен бути числом');
}
if (numbers.length === 0) {
throw new TypeError('Потрібно передати принаймні одне число для множення');
}
numbers.forEach((num, index) => {
if (typeof num !== 'number') {
throw new TypeError(`Аргумент ${index + 2} повинен бути числом, отримано ${typeof num}`);
}
});
return numbers.map(num => num * multiplier);
}
console.log(multiply(2, 1, 2, 3, 4)); // [2, 4, 6, 8]
3. Валідація функцій вищого порядку
function mapWithValidation(array, mapFunction) {
if (!Array.isArray(array)) {
throw new TypeError('Перший аргумент повинен бути масивом');
}
if (typeof mapFunction !== 'function') {
throw new TypeError('Другий аргумент повинен бути функцією');
}
return array.map((item, index) => {
try {
return mapFunction(item, index);
} catch (error) {
throw new TypeError(`Помилка в функції мапінгу на індексі ${index}: ${error.message}`);
}
});
}
// Використання
const numbers = [1, 2, 3, 4];
const doubleFunction = x => x * 2;
console.log(mapWithValidation(numbers, doubleFunction)); // [2, 4, 6, 8]
Створення власних підкласів TypeError
class ValidationTypeError extends TypeError {
constructor(field, expectedType, actualType) {
super(`Поле "${field}" повинно бути типу ${expectedType}, отримано ${actualType}`);
this.name = 'ValidationTypeError';
this.field = field;
this.expectedType = expectedType;
this.actualType = actualType;
}
}
function validateProduct(product) {
if (typeof product.name !== 'string') {
throw new ValidationTypeError('name', 'string', typeof product.name);
}
if (typeof product.price !== 'number') {
throw new ValidationTypeError('price', 'number', typeof product.price);
}
return true;
}
try {
validateProduct({ name: 123, price: "50" });
} catch (error) {
if (error instanceof ValidationTypeError) {
console.error(`Помилка валідації: ${error.message}`);
console.error(`Поле: ${error.field}`);
}
}
Кращі практики обробки TypeError
1. Раннє виявлення помилок
function processPayment(amount, currency = 'UAH') {
// Валідація на початку функції
if (typeof amount !== 'number' || amount <= 0) {
throw new TypeError('Сума платежу повинна бути додатним числом');
}
if (typeof currency !== 'string' || currency.length !== 3) {
throw new TypeError('Валюта повинна бути рядком з 3 символів');
}
// Основна логіка функції
return {
amount,
currency: currency.toUpperCase(),
timestamp: new Date()
};
}
2. Інформативні повідомлення про помилки
function setUserPermissions(user, permissions) {
if (typeof user !== 'object' || user === null) {
throw new TypeError(
`Параметр user повинен бути об'єктом. ` +
`Отримано: ${user === null ? 'null' : typeof user}. ` +
`Приклад правильного використання: setUserPermissions({id: 1, name: 'Іван'}, ['read', 'write'])`
);
}
if (!Array.isArray(permissions)) {
throw new TypeError(
`Параметр permissions повинен бути масивом рядків. ` +
`Отримано: ${typeof permissions}. ` +
`Приклад: ['read', 'write', 'delete']`
);
}
// Перевірка типів елементів масиву
const invalidPermissions = permissions.filter(p => typeof p !== 'string');
if (invalidPermissions.length > 0) {
throw new TypeError(
`Всі дозволи повинні бути рядками. ` +
`Знайдено неправильні типи: ${invalidPermissions.map(p => typeof p).join(', ')}`
);
}
return { ...user, permissions };
}
3. Захисне програмування з graceful degradation
function safeStringOperation(value, operation = 'uppercase') {
// Спроба конвертації до рядка
let stringValue;
try {
if (value === null || value === undefined) {
throw new TypeError('Значення не може бути null або undefined');
}
stringValue = String(value);
} catch (error) {
throw new TypeError(`Неможливо конвертувати значення в рядок: ${error.message}`);
}
// Валідація операції
const validOperations = ['uppercase', 'lowercase', 'trim', 'reverse'];
if (!validOperations.includes(operation)) {
throw new TypeError(
`Невідома операція: ${operation}. ` +
`Доступні операції: ${validOperations.join(', ')}`
);
}
// Виконання операції
switch (operation) {
case 'uppercase':
return stringValue.toUpperCase();
case 'lowercase':
return stringValue.toLowerCase();
case 'trim':
return stringValue.trim();
case 'reverse':
return stringValue.split('').reverse().join('');
default:
return stringValue;
}
}
// Приклади використання
console.log(safeStringOperation('hello', 'uppercase')); // HELLO
console.log(safeStringOperation(123, 'reverse')); // 321
Інтеграція з TypeScript
Якщо ви працюєте з TypeScript, TypeError все ще корисний для runtime валідації:
// TypeScript дає перевірку типів на етапі компіляції
function processUserData(data: unknown): { name: string; age: number } {
// Runtime валідація для даних, що приходять ззовні
if (typeof data !== 'object' || data === null) {
throw new TypeError('Дані повинні бути об\'єктом');
}
const obj = data as Record<string, unknown>;
if (typeof obj.name !== 'string') {
throw new TypeError('Поле name повинно бути рядком');
}
if (typeof obj.age !== 'number') {
throw new TypeError('Поле age повинно бути числом');
}
return { name: obj.name, age: obj.age };
}
Тестування TypeError
// Приклад тестування з Jest
describe('calculateArea function', () => {
test('should throw TypeError for non-number width', () => {
expect(() => {
calculateArea('5', 10);
}).toThrow(TypeError);
expect(() => {
calculateArea('5', 10);
}).toThrow('Ширина повинна бути числом');
});
test('should throw TypeError for negative dimensions', () => {
expect(() => {
calculateArea(-5, 10);
}).toThrow(TypeError);
});
test('should calculate area correctly for valid inputs', () => {
expect(calculateArea(5, 10)).toBe(50);
});
});
Відмінності від інших типів помилок
TypeError vs RangeError
// TypeError - неправильний тип
function processAge(age) {
if (typeof age !== 'number') {
throw new TypeError('Вік повинен бути числом');
}
if (age < 0 || age > 150) {
throw new RangeError('Вік повинен бути в діапазоні 0-150 років');
}
}
TypeError vs SyntaxError
// TypeError - помилка виконання
throw new TypeError('Неправильний тип аргументу');
// SyntaxError - помилка парсингу (зазвичай не створюється вручну)
// eval('let x = ;'); // SyntaxError: Unexpected token ';'
Моніторинг та логування TypeError
function createErrorLogger() {
return function logTypeError(error, context = {}) {
if (error instanceof TypeError) {
console.error('TypeError detected:', {
message: error.message,
stack: error.stack,
context,
timestamp: new Date().toISOString()
});
// Відправка на сервіс моніторингу
// sendToErrorService(error, context);
}
};
}
const logError = createErrorLogger();
function risky_operation(input) {
try {
// Операція що може викинути TypeError
return processUserInput(input);
} catch (error) {
logError(error, { input, functionName: 'risky_operation' });
throw error; // Перекидаємо помилку далі
}
}
Висновок
TypeError є потужним інструментом для створення надійного JavaScript коду:
✅ Коли використовувати TypeError:
- При валідації типів параметрів функцій
- При перевірці структури об'єктів
- При роботі з зовнішніми API або користувацьким вводом
- При створенні бібліотек, що потребують строгої типізації
✅ Кращі практики:
- Надавайте інформативні повідомлення про помилки
- Валідуйте дані якомога раніше
- Використовуйте TypeError разом із TypeScript для повної безпеки типів
- Тестуйте всі випадки виникнення TypeError
- Логуйте помилки для моніторингу
✅ Пам'ятайте:
- TypeError допомагає виявляти помилки на раннім етапі
- Хороші повідомлення про помилки прискорюють розробку
- Валідація типів робить код більш передбачуваним
- TypeError - це частина захисного програмування
Правильне використання TypeError зробить ваш код більш надійним, зрозумілим та простішим у підтримці!