← JavaScript/Обработка ошибок: try/catch#82 из 383← ПредыдущийСледующий →+25 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: JS базаПрактика: async и сетьТермин: Closure

Обработка ошибок: try/catch

Какую проблему решает try/catch

Ты пишешь API-клиент для интернет-магазина: он принимает JSON от сервера, парсит его и достаёт цену товара. Что если JSON сломан? Что если поле цены отсутствует? Что если сервер вернул null?

Без обработки ошибок приложение просто упадёт — белый экран, потеря данных, расстроенный пользователь. try/catch позволяет перехватить ошибку, обработать её элегантно и продолжить работу.

На основе предыдущих уроков

  • «JSON» — JSON.parse() часто бросает SyntaxError на невалидных данных
  • «Функции» — функции могут бросать ошибки через throw
  • «if/else» — условная логика в блоках catch
  • Базовый синтаксис

    try {
      // Код, который может бросить ошибку
      const data = JSON.parse(userInput)
      console.log(data.price)
    } catch (error) {
      // Выполняется только при ошибке в try
      console.error('Не удалось разобрать данные:', error.message)
    } finally {
      // Выполняется ВСЕГДА — и при ошибке, и без неё
      console.log('Попытка обработки завершена')
    }

    Объект ошибки

    try {
      null.property  // TypeError
    } catch (e) {
      console.log(e.name)     // 'TypeError'
      console.log(e.message)  // "Cannot read properties of null (reading 'property')"
      console.log(e.stack)    // полный стек вызовов — полезно при отладке
    }

    Встроенные типы ошибок

  • SyntaxError — JSON.parse('{') — неверный синтаксис
  • TypeError — null.foo, вызов не-функции — неверный тип
  • RangeError — new Array(-1) — значение вне диапазона
  • ReferenceError — обращение к необъявленной переменной
  • throw — бросаем собственные ошибки

    Не только JavaScript может бросать ошибки — ты тоже можешь:

    function parseOrderAmount(value) {
      const amount = Number(value)
    
      if (isNaN(amount)) {
        throw new TypeError(`Сумма заказа должна быть числом, получили: ${value}`)
      }
      if (amount <= 0) {
        throw new RangeError(`Сумма заказа должна быть положительной, получили: ${amount}`)
      }
    
      return amount
    }

    Бросать можно что угодно — строку, объект, число. Но всегда бросай объект Error (или его наследника): только у него есть удобный .stack.

    Паттерн re-throw (перебрасывание)

    Ловим только то, с чем умеем работать. Остальное — пробрасываем выше:

    function loadConfig(raw) {
      try {
        return JSON.parse(raw)
      } catch (e) {
        if (e instanceof SyntaxError) {
          // Знаем как обработать — логируем и возвращаем дефолт
          console.warn('Конфиг сломан, используем дефолтный')
          return {}
        }
        throw e  // Неожиданная ошибка — пробрасываем выше
      }
    }

    finally — гарантированная очистка ресурсов

    async function processOrder(orderId) {
      let dbConnection = null
      try {
        dbConnection = await openConnection()
        const order = await dbConnection.query(`SELECT * FROM orders WHERE id = ${orderId}`)
        return order
      } catch (e) {
        console.error('Ошибка БД:', e.message)
        return null
      } finally {
        // Закроется в любом случае — и при успехе, и при ошибке
        dbConnection?.close()
      }
    }

    Типичные ошибки

    1. Поглощение ошибок — молчаливый catch:

    // Сломано — ошибка проглочена, невозможно отладить:
    try {
      riskyOperation()
    } catch (e) {}   // пустой catch — худшая практика!
    
    // Исправлено — хотя бы логируй:
    try {
      riskyOperation()
    } catch (e) {
      console.error('Операция упала:', e.message)
      // или throw e, если не знаешь как обработать
    }

    2. try/catch не ловит асинхронные ошибки:

    // Сломано — ошибка в setTimeout не поймается:
    try {
      setTimeout(() => {
        throw new Error('Это не поймается!')
      }, 100)
    } catch (e) {
      console.log('Не сработает')  // не выполнится
    }
    
    // Исправлено — используй async/await:
    try {
      await asyncOperation()
    } catch (e) {
      console.error(e.message)
    }

    3. Лишний try/catch вместо проверки данных:

    // Сломано — try/catch не замена валидации:
    function getPrice(product) {
      try {
        return product.price.toFixed(2)
      } catch (e) {
        return '0.00'
      }
    }
    
    // Исправлено — проверяй данные явно:
    function getPrice(product) {
      if (!product || typeof product.price !== 'number') return '0.00'
      return product.price.toFixed(2)
    }

    В реальных проектах

  • API-запросы: каждый fetch обёрнут в try/catch для обработки сетевых ошибок
  • JSON.parse: всегда в try/catch — данные от сервера или пользователя могут быть сломаны
  • Express.js: middleware обработки ошибок принимает (err, req, res, next)
  • React: ErrorBoundary — компонент-«поймушка» для ошибок рендеринга
  • Примеры

    Парсинг и валидация данных заказа с обработкой разных ошибок

    function parseOrderData(json) {
      // Шаг 1: парсинг JSON — может быть SyntaxError
      let data
      try {
        data = JSON.parse(json)
      } catch (e) {
        throw new SyntaxError('Неверный формат данных заказа: ' + e.message)
      }
    
      // Шаг 2: валидация полей — бросаем TypeError при несоответствии типов
      if (!data.productId || typeof data.productId !== 'number') {
        throw new TypeError('Поле productId обязательно и должно быть числом')
      }
      if (!data.quantity || data.quantity <= 0) {
        throw new RangeError('Количество товара должно быть положительным')
      }
      if (!data.customerEmail || !data.customerEmail.includes('@')) {
        throw new TypeError('Поле customerEmail должно быть валидным email')
      }
    
      return {
        productId: data.productId,
        quantity: data.quantity,
        email: data.customerEmail.toLowerCase(),
      }
    }
    
    // Тестируем разные входные данные
    const testCases = [
      '{"productId":42,"quantity":2,"customerEmail":"ivan@shop.ru"}',  // ОК
      '{broken json',                                                    // SyntaxError
      '{"productId":"abc","quantity":2,"customerEmail":"ok@ok.com"}',   // TypeError
      '{"productId":1,"quantity":-5,"customerEmail":"ok@ok.com"}',      // RangeError
    ]
    
    testCases.forEach((input, i) => {
      try {
        const order = parseOrderData(input)
        console.log(`Тест ${i + 1} ОК: заказ #${order.productId} x${order.quantity}`)
      } catch (e) {
        if (e instanceof SyntaxError) {
          console.log(`Тест ${i + 1} — Формат: ${e.message}`)
        } else if (e instanceof TypeError) {
          console.log(`Тест ${i + 1} — Тип данных: ${e.message}`)
        } else if (e instanceof RangeError) {
          console.log(`Тест ${i + 1} — Диапазон: ${e.message}`)
        } else {
          throw e  // Неожиданная ошибка — пробрасываем
        }
      }
    })
    // Тест 1 ОК: заказ #42 x2
    // Тест 2 — Формат: Неверный формат данных заказа: ...
    // Тест 3 — Тип данных: Поле productId обязательно и должно быть числом
    // Тест 4 — Диапазон: Количество товара должно быть положительным

    Обработка ошибок: try/catch

    Какую проблему решает try/catch

    Ты пишешь API-клиент для интернет-магазина: он принимает JSON от сервера, парсит его и достаёт цену товара. Что если JSON сломан? Что если поле цены отсутствует? Что если сервер вернул null?

    Без обработки ошибок приложение просто упадёт — белый экран, потеря данных, расстроенный пользователь. try/catch позволяет перехватить ошибку, обработать её элегантно и продолжить работу.

    На основе предыдущих уроков

  • «JSON» — JSON.parse() часто бросает SyntaxError на невалидных данных
  • «Функции» — функции могут бросать ошибки через throw
  • «if/else» — условная логика в блоках catch
  • Базовый синтаксис

    try {
      // Код, который может бросить ошибку
      const data = JSON.parse(userInput)
      console.log(data.price)
    } catch (error) {
      // Выполняется только при ошибке в try
      console.error('Не удалось разобрать данные:', error.message)
    } finally {
      // Выполняется ВСЕГДА — и при ошибке, и без неё
      console.log('Попытка обработки завершена')
    }

    Объект ошибки

    try {
      null.property  // TypeError
    } catch (e) {
      console.log(e.name)     // 'TypeError'
      console.log(e.message)  // "Cannot read properties of null (reading 'property')"
      console.log(e.stack)    // полный стек вызовов — полезно при отладке
    }

    Встроенные типы ошибок

  • SyntaxError — JSON.parse('{') — неверный синтаксис
  • TypeError — null.foo, вызов не-функции — неверный тип
  • RangeError — new Array(-1) — значение вне диапазона
  • ReferenceError — обращение к необъявленной переменной
  • throw — бросаем собственные ошибки

    Не только JavaScript может бросать ошибки — ты тоже можешь:

    function parseOrderAmount(value) {
      const amount = Number(value)
    
      if (isNaN(amount)) {
        throw new TypeError(`Сумма заказа должна быть числом, получили: ${value}`)
      }
      if (amount <= 0) {
        throw new RangeError(`Сумма заказа должна быть положительной, получили: ${amount}`)
      }
    
      return amount
    }

    Бросать можно что угодно — строку, объект, число. Но всегда бросай объект Error (или его наследника): только у него есть удобный .stack.

    Паттерн re-throw (перебрасывание)

    Ловим только то, с чем умеем работать. Остальное — пробрасываем выше:

    function loadConfig(raw) {
      try {
        return JSON.parse(raw)
      } catch (e) {
        if (e instanceof SyntaxError) {
          // Знаем как обработать — логируем и возвращаем дефолт
          console.warn('Конфиг сломан, используем дефолтный')
          return {}
        }
        throw e  // Неожиданная ошибка — пробрасываем выше
      }
    }

    finally — гарантированная очистка ресурсов

    async function processOrder(orderId) {
      let dbConnection = null
      try {
        dbConnection = await openConnection()
        const order = await dbConnection.query(`SELECT * FROM orders WHERE id = ${orderId}`)
        return order
      } catch (e) {
        console.error('Ошибка БД:', e.message)
        return null
      } finally {
        // Закроется в любом случае — и при успехе, и при ошибке
        dbConnection?.close()
      }
    }

    Типичные ошибки

    1. Поглощение ошибок — молчаливый catch:

    // Сломано — ошибка проглочена, невозможно отладить:
    try {
      riskyOperation()
    } catch (e) {}   // пустой catch — худшая практика!
    
    // Исправлено — хотя бы логируй:
    try {
      riskyOperation()
    } catch (e) {
      console.error('Операция упала:', e.message)
      // или throw e, если не знаешь как обработать
    }

    2. try/catch не ловит асинхронные ошибки:

    // Сломано — ошибка в setTimeout не поймается:
    try {
      setTimeout(() => {
        throw new Error('Это не поймается!')
      }, 100)
    } catch (e) {
      console.log('Не сработает')  // не выполнится
    }
    
    // Исправлено — используй async/await:
    try {
      await asyncOperation()
    } catch (e) {
      console.error(e.message)
    }

    3. Лишний try/catch вместо проверки данных:

    // Сломано — try/catch не замена валидации:
    function getPrice(product) {
      try {
        return product.price.toFixed(2)
      } catch (e) {
        return '0.00'
      }
    }
    
    // Исправлено — проверяй данные явно:
    function getPrice(product) {
      if (!product || typeof product.price !== 'number') return '0.00'
      return product.price.toFixed(2)
    }

    В реальных проектах

  • API-запросы: каждый fetch обёрнут в try/catch для обработки сетевых ошибок
  • JSON.parse: всегда в try/catch — данные от сервера или пользователя могут быть сломаны
  • Express.js: middleware обработки ошибок принимает (err, req, res, next)
  • React: ErrorBoundary — компонент-«поймушка» для ошибок рендеринга
  • Примеры

    Парсинг и валидация данных заказа с обработкой разных ошибок

    function parseOrderData(json) {
      // Шаг 1: парсинг JSON — может быть SyntaxError
      let data
      try {
        data = JSON.parse(json)
      } catch (e) {
        throw new SyntaxError('Неверный формат данных заказа: ' + e.message)
      }
    
      // Шаг 2: валидация полей — бросаем TypeError при несоответствии типов
      if (!data.productId || typeof data.productId !== 'number') {
        throw new TypeError('Поле productId обязательно и должно быть числом')
      }
      if (!data.quantity || data.quantity <= 0) {
        throw new RangeError('Количество товара должно быть положительным')
      }
      if (!data.customerEmail || !data.customerEmail.includes('@')) {
        throw new TypeError('Поле customerEmail должно быть валидным email')
      }
    
      return {
        productId: data.productId,
        quantity: data.quantity,
        email: data.customerEmail.toLowerCase(),
      }
    }
    
    // Тестируем разные входные данные
    const testCases = [
      '{"productId":42,"quantity":2,"customerEmail":"ivan@shop.ru"}',  // ОК
      '{broken json',                                                    // SyntaxError
      '{"productId":"abc","quantity":2,"customerEmail":"ok@ok.com"}',   // TypeError
      '{"productId":1,"quantity":-5,"customerEmail":"ok@ok.com"}',      // RangeError
    ]
    
    testCases.forEach((input, i) => {
      try {
        const order = parseOrderData(input)
        console.log(`Тест ${i + 1} ОК: заказ #${order.productId} x${order.quantity}`)
      } catch (e) {
        if (e instanceof SyntaxError) {
          console.log(`Тест ${i + 1} — Формат: ${e.message}`)
        } else if (e instanceof TypeError) {
          console.log(`Тест ${i + 1} — Тип данных: ${e.message}`)
        } else if (e instanceof RangeError) {
          console.log(`Тест ${i + 1} — Диапазон: ${e.message}`)
        } else {
          throw e  // Неожиданная ошибка — пробрасываем
        }
      }
    })
    // Тест 1 ОК: заказ #42 x2
    // Тест 2 — Формат: Неверный формат данных заказа: ...
    // Тест 3 — Тип данных: Поле productId обязательно и должно быть числом
    // Тест 4 — Диапазон: Количество товара должно быть положительным

    Задание

    Ты разрабатываешь сервис онлайн-банка. Напиши функцию `transfer(from, to, amount)`, которая переводит деньги между счетами. Функция должна бросать: - `TypeError` — если `amount` не является числом - `RangeError` — если `amount <= 0` или если на счёте `from` недостаточно средств - Возвращать объект `{ from, to, amount, newBalance }` при успехе Обработай все случаи в try/catch с разными сообщениями.

    Подсказка

    typeof amount !== "number" — для TypeError. amount <= 0 — для RangeError (нулевая/отрицательная сумма). from.balance < amount — для RangeError (недостаточно средств). В catch проверяй e instanceof TypeError и e instanceof RangeError.

    Загружаем среду выполнения...
    Загружаем AI-помощника...