Переход от instanceof к Error.isError: надёжная проверка ошибок в JavaScript

Почему instanceof возвращает false для настоящих ошибок из других рилмов, а системы логирования теряют stack trace'ы? Переходим на Error.isError() — надёжный способ проверки ошибок в JavaScript, который работает везде: в iframe, воркерах и браузерных расширениях.

Введение

На протяжении многих лет основным инструментом для проверки принадлежности значения к типу Error в JavaScript была операция instanceof. Конструкция err instanceof Error стала идиоматическим стандартом, который в большинстве повседневных сценариев работает предсказуемо.

Однако этот подход имеет фундаментальное ограничение, связанное с моделью выполнения JavaScript. Код может выполняться в различных глобальных контекстах (рилмах, realms), каждый из которых обладает собственным набором встроенных конструкторов, включая Error. Когда объект ошибки создаётся в одном контексте выполнения (например, внутри iframe или web-worker'а), а проверяется в другом, оператор instanceof возвращает false, даже если перед ним полноценный объект ошибки.

Такое поведение приводит к трудно диагностируемым сбоям, которые часто проявляются только в продакшене: ошибки перестают логироваться, теряются stack trace'ы, а системы мониторинга фиксируют «пустые» или «неопознанные» объекты.

Для решения этой проблемы в спецификацию JavaScript был добавлен новый статический метод — Error.isError(). Он предоставляет надёжный, не зависящий от рилмов способ идентификации ошибок, проверяя внутренний «бренд» объекта, недоступный из пользовательского кода. В данной статье мы рассмотрим причины несостоятельности instanceof в кросс-контекстных сценариях и объясним, почему Error.isError() является более безопасной и современной альтернативой.

TL;DR

Оператор instanceof Error ненадёжен при проверке ошибок, созданных в других глобальных контекстах (iframe, веб-воркеры, SSR, браузерные расширения). Он возвращает false для настоящих объектов ошибки из другого рилма, что приводит к потере данных в логах и молчаливым сбоям в продакшене.

Новый метод Error.isError(value) решает эту проблему:

// Было (ненадёжно на границах рилмов)
if (err instanceof Error) { /* может не сработать */ }

// Стало (работает везде)
if (Error.isError(err)) { /* надёжно */ }

Где менять в первую очередь: глобальные обработчики ошибок, системы логирования, код, работающий с iframe и воркерами.

Как внедрять: используйте функцию-обёртку с проверкой поддержки:

function isError(value) {
return typeof Error.isError === 'function'
? Error.isError(value)
: value instanceof Error;
}

Проблема множественных рилмов

JavaScript может выполняться не в одном, а в нескольких независимых глобальных окружениях, которые в спецификации называются рилмами (realms). Каждый рилм представляет собой изолированную среду с собственным набором глобальных объектов и конструкторов: Object, Array, Function и, что критически важно для нашей темы, — Error.

Ситуация, когда два значения являются объектами ошибки с точки зрения семантики кода, но при этом были созданы в разных контекстах выполнения, приводит к неожиданному результату: JavaScript рассматривает их как значения, ассоциированные с разными конструкторами. Это не баг в реализации, а особенность работы самого языка, которая становится источником ошибок при переходе границ между контекстами выполнения.

Типичные сценарии, в которых возникает пересечение рилмов:

  • Взаимодействие с iframe: код в родительском окне обрабатывает объекты, созданные внутри iframe.
  • Использование Web Workers: основной поток и воркер существуют в разных контекстах выполнения.
  • Серверный рендеринг (SSR) и гибридные рантаймы: код выполняется в окружении, которое может эмулировать или создавать изолированные контексты.
  • Браузерные расширения: их скрипты взаимодействуют со страницами, каждая из которых живёт в своём рилме.
  • Среды тестирования: фреймворки часто изолируют тестовые файлы в отдельных контекстах.

Проблема в том, что сбой не сопровождается явной ошибкой. Проверка err instanceof Error просто возвращает false, и выполнение программы продолжается. Последствия обнаруживаются позже, на этапе анализа логов, когда выясняется, что система мониторинга не зафиксировала критическую ошибку или все зарегистрированные объекты ошибок оказались «пустыми».

В следующем разделе мы детально разберём, почему именно instanceof не справляется с кросс-рилмными сценариями, и покажем это на конкретном примере.

Почему instanceof не работает через границы рилмов

Оператор instanceof проверяет наличие конструктора в цепочке прототипов объекта. В частности, выражение err instanceof Error возвращает true только в том случае, если прототип объекта err содержит ссылку на конструктор Error из того же рилма, в котором выполняется проверка.

Когда объект ошибки создаётся в другом контексте выполнения, его прототипная цепочка ссылается на конструктор Error из этого другого iframe/воркера. Для оператора instanceof, выполняющегося в исходном рилме, этот конструктор является совершенно другим объектом, не совпадающим с локальным Error. Результатом становится false, несмотря на то, что проверяемое значение является полноценным объектом ошибки.

Рассмотрим классический пример с iframe:

const iframe = document.createElement('iframe');
document.body.appendChild(iframe);

// Создаём ошибку, используя конструктор из контекста iframe
const err = new iframe.contentWindow.Error('Ошибка в iframe');

// Проверяем в родительском контексте
console.log(err instanceof Error); // false

С точки зрения разработчика, err — это объект ошибки. Он содержит сообщение, может содержать stack trace и ведёт себя как ожидается. Однако проверка не проходит, поскольку прототип err ведёт к iframe.contentWindow.Error, а не к window.Error.

Такое поведение создаёт ряд проблем:

  • Ошибки, созданные в iframe, воркерах или других изолированных контекстах, могут быть не распознаны в глобальных обработчиках.
  • Библиотеки для логирования, использующие instanceof для форматирования ошибок, могут пропускать релевантную информацию.
  • Системы мониторинга могут классифицировать настоящие ошибки как обычные объекты, теряя stack trace и контекст.

Важно подчеркнуть, что instanceof не является «неправильным» оператором. Он корректно выполняет свою задачу в пределах одного рилма. Проблема возникает именно на стыке разных контекстов выполнения — там, где границы рилмов становятся невидимыми для разработчика, но остаются значимыми для языка.

Следующий раздел будет посвящён новому методу Error.isError(), который решает описанную проблему, проверяя не принадлежность к конкретному конструктору, а наличие внутреннего идентификатора ошибки.

Метод Error.isError()

Для решения проблемы кросс-рилмной идентификации ошибок в спецификацию JavaScript был добавлен статический метод Error.isError(value). В отличие от оператора instanceof, который проверяет прототипную цепочку относительно конкретного конструктора, новый метод выполняет проверку наличия внутреннего приватного поля (brand), инициализируемого конструктором Error. Этот механизм недоступен из пользовательского кода и аналогичен тому, который используется в Array.isArray().

Сигнатура и поведение

Метод принимает один аргумент произвольного типа и возвращает логическое значение:

  • true — если аргумент является настоящим объектом ошибки (включая созданные в других рилмах).
  • false — во всех остальных случаях, включая объекты, лишь имитирующие структуру.
  • Метод гарантированно не выбрасывает исключений даже при передаче некорректных или неожиданных значений.

Примеры использования

Базовые проверки ведут себя предсказуемо и интуитивно:

Error.isError(new Error('Сообщение'))          // true
Error.isError(new TypeError('Ошибка типа')) // true
Error.isError('просто строка') // false
Error.isError({ message: 'Похоже на ошибку' }) // false

Особого внимания заслуживает случай с объектом, наследующим от Error.prototype:

const fakeError = Object.create(Error.prototype);
Error.isError(fakeError); // false

Даже если объект искусственно наделён прототипом ошибки, метод корректно возвращает false, поскольку объект не был создан как экземпляр ошибки и не имеет соответствующего внутреннего «бренда». Для сравнения: оператор instanceof Error в такой ситуации вернул бы true, так как он проверяет только наличие Error.prototype в цепочке, а не подлинность объекта.

Кросс-рилмная идентификация

Главное преимущество Error.isError() раскрывается при работе с объектами из других рилмов:

const iframe = document.createElement('iframe');
document.body.appendChild(iframe);

const err = new iframe.contentWindow.Error('Ошибка из iframe');
Error.isError(err); // true

Независимо от того, в каком контексте выполнения была создана ошибка, метод гарантированно возвращает true, что соответствует ожиданиям разработчика.

Граничные случаи

В браузерных окружениях Error.isError() также возвращает true для объектов DOMException. Хотя формально DOMException не является подклассом Error (его цепочка прототипов не ведёт к конструктору ошибки), платформа рассматривает его как ошибку для всех целей брендированной проверки.

Пользовательские классы ошибок

Метод корректно работает с наследниками встроенного класса Error:

class CustomError extends Error {}

const err = new CustomError('Пользовательская ошибка');
Error.isError(err); // true

Если пользовательский класс корректно расширяет Error, его экземпляры распознаются как ошибки даже при пересечении границ рилмов.

В следующем разделе мы рассмотрим практические рекомендации по применению нового метода и стратегии его внедрения в существующие кодовые базы.

Рекомендации по применению

Error.isError() не требует замены каждого instanceof Error в проекте. Решение о его применении должно основываться на контексте выполнения кода и потенциальном риске кросс-рилмных сценариев.

Где использовать Error.isError()

Наиболее оправдано применение нового метода в тех местах, куда ошибки могут попадать из внешних или изолированных контекстов:

  • Глобальные обработчики ошибок — функции, регистрируемые через window.onerror или process.on('uncaughtException'), которые могут получать ошибки из любых источников.
  • Системы логирования и мониторинга — код, который форматирует, сериализует или отправляет ошибки во внешние сервисы.
  • Библиотеки для работы с промисами — обработчики catch, особенно в универсальных утилитах.
  • Тестовые фреймворки и раннеры — код, перехватывающий исключения в тестах, которые могут выполняться в изолированных средах.
  • Код, работающий с iframe, Web Workers или Service Workers — при передаче ошибок между основным потоком и изолированными контекстами.
  • SSR-приложения и edge-рантаймы — где серверный и клиентский код могут выполняться в окружениях с разными глобальными объектами.
  • Браузерные расширения — при взаимодействии скрипта расширения с кодом страницы.

В перечисленных сценариях использование instanceof Error может приводить к не обнаружению реальных ошибок, что делает Error.isError() более безопасным выбором.

Где можно оставить instanceof

Внутри модулей, которые не пересекают границы рилмов, использование instanceof остаётся допустимым. Примеры таких ситуаций:

  • Проверка ошибок, созданных в том же файле или модуле.
  • Внутренние проверки в реализации библиотеки, не принимающей ошибки извне.
  • Код, выполняющийся строго в одном контексте (например, серверный код, не использующий воркеры или изоляцию).

В этих случаях дополнительная надёжность Error.isError() не даёт преимуществ, а использование instanceof не создаёт рисков.

Стратегия внедрения Error.isError()

Для постепенного перехода на новый метод без масштабного рефакторинга рекомендуется использовать функцию-обёртку:

function isError(value) {
return typeof Error.isError === 'function'
? Error.isError(value)
: value instanceof Error;
}

Такой подход обеспечивает:

  • Корректную работу в современных окружениях.
  • Приемлемое поведение в старых средах через fallback.
  • Единую точку изменения, когда поддержка метода станет универсальной.

Внедрение рекомендуется начинать с пограничных областей приложения — глобальных обработчиков, систем логирования, точек интеграции с внешними контекстами. Именно здесь кросс-рилмные ошибки наиболее вероятны, а цена их пропуска максимальна.

Проверка типов в TypeScript

Благодаря тому, что Error.isError() работает как type guard, его использование в TypeScript делает обработку ошибок не только надёжной, но и типобезопасной. В отличие от instanceof, он корректно сужает тип даже для ошибок из других рилмов.

try {
// ... какой-то код
} catch (e: unknown) {
if (Error.isError(e)) {
// e теперь имеет тип 'Error'
console.log(e.message); // Безопасно и с автодополнением
} else {
// Обработка не-ошибок (строк, чисел и т.д.)
console.log("Поймано нечто иное:", e);
}
}

Проверка поддержки в средах выполнения

Поскольку метод Error.isError() пока не является частью Baseline (на момент написания статьи поддерживается не во всех основных браузерах), необходима проверка наличия функциональности:

if (typeof Error.isError === 'function') {
// Используем современный метод
return Error.isError(value);
} else {
// Используем fallback
return value instanceof Error;
}

Актуальная информация о поддержке:

  • ✅ Chrome 134+, Edge 134+, Firefox 138+
  • ✅ Node.js 24.3+
  • ⚠️ Safari 18.4 (поддержка добавлена, но может быть неполной)

В следующем разделе мы подведём итоги и сформулируем основные выводы.

Заключение

Оператор instanceof Error надёжно работает в пределах одного рилма, но даёт ложноотрицательные результаты при проверке ошибок, созданных в других контекстах: iframe, веб-воркерах, средах SSR или браузерных расширениях. Такие сбои не сопровождаются явными ошибками, но приводят к потере данных в системах логирования и некорректной обработке исключений в production-среде.

Метод Error.isError() решает эту проблему, выполняя проверку внутреннего «бренда» объекта, инициализируемого конструктором Error. Он гарантированно возвращает true для любого настоящего объекта ошибки независимо от контекста и false для всех остальных значений, включая объекты, лишь имитирующие структуру ошибки через прототипное наследование.

Для внедрения достаточно заменить instanceof на Error.isError() в критических точках (глобальные обработчики, системы логирования, код с изолированными контекстами), а функцию-обёртку использовать для совместимости со старыми окружениями. Такая замена в пограничных областях приложения устраняет целую категорию скрытых сбоев, делая обработку ошибок предсказуемой, а системы мониторинга — надёжнее.

Часто задаваемые вопросы

Чем Error.isError() отличается от instanceof Error?

instanceof Error проверяет наличие конструктора Error в цепочке прототипов и работает только в пределах одного выполнения. Error.isError() проверяет внутренний «бренд» объекта, созданный конструктором Error, и корректно работает с ошибками из любых рилмов (iframe, воркеры, SSR), а также отклоняет объекты, лишь имитирующие ошибку через Object.create(Error.prototype).

Можно ли использовать Error.isError() с пользовательскими классами ошибок?

Да, если пользовательский класс наследуется от Error через extends. Метод вернёт true для экземпляров такого класса, даже если они созданы в другом рилме.

Что вернёт Error.isError() для DOMException?

true. Хотя DOMException формально не является подклассом Error, платформа рассматривает его как ошибку для всех целей брендированной проверки, что удобно для систем логирования.

Как быть, если нужно поддерживать старые браузеры без Error.isError()?

Используйте функцию-обёртку с проверкой наличия метода:

function isError(value) {
return typeof Error.isError === 'function'
? Error.isError(value)
: value instanceof Error;
}
Где в коде нужно заменить instanceof на Error.isError() в первую очередь?

В глобальных обработчиках ошибок (window.onerror, process.on('uncaughtException')), системах логирования, коде, работающем с iframe, веб-воркерами, SSR и браузерными расширениями. В изолированных модулях, не пересекающих границы рилмов, instanceof можно оставить.

Поддерживается ли Error.isError() в Node.js?

Да, начиная с Node.js 24.3+.

Почему Error.isError(Object.create(Error.prototype)) возвращает false?

Потому что у объекта нет внутреннего «бренда», который инициализируется только при вызове конструктора Error (или его наследников). Наследование прототипа не делает объект настоящей ошибкой.

Можно ли проверить, поддерживает ли среда Error.isError()?

Да, простой проверкой:

if (typeof Error.isError === 'function') {
// поддерживается
}

Комментарии


Дополнительные материалы

Предыдущая Статья

React: Какой useEffect запускается первым?

Следующая Статья

Что такое this в JavaScript