Середнійerror-handlingTypeErrorexceptionsdebuggingbest-practices

Що таке throw new TypeError і де це використовувати?

Детальний розбір TypeError в JavaScript: коли, чому та як правильно використовувати цей тип помилки

Що таке 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 зробить ваш код більш надійним, зрозумілим та простішим у підтримці!