Error.isError() — realm을 넘나드는 안전한 에러 검사 API

8 min read
JavaScriptError.isErrorTC39Cross-Realm
Error.isError() — realm을 넘나드는 안전한 에러 검사 API

instanceof Error를 믿어도 되는지 확신이 없었던 적이 있을 것이다. iframe 안에서 생성된 에러, Web Worker에서 던져진 에러 — 이것들이 instanceof Error를 통과하지 못한다는 걸 처음 알았을 때의 당혹감은 꽤 크다. Chrome 134, Node.js 24.3부터 이 문제를 정식으로 해결하는 Error.isError()가 도입됐다.

instanceof Error의 정확한 실패 지점

instanceof 연산자는 프로토타입 체인을 따라 올라가면서 일치하는 생성자를 찾는다. 그런데 이 검사는 **"같은 realm의 Error 생성자를 공유하는가"**를 묻는 것이다.

[💡 잠깐! 이 용어는?] Realm: JavaScript 엔진이 관리하는 독립적인 실행 환경이다. 전역 객체(window, globalThis), 내장 생성자(Array, Error, Promise), 표준 프로토타입이 realm마다 별도로 존재한다. iframe, Web Worker, Node.js의 vm.runInNewContext, 브라우저 익스텐션의 content script가 각각 독립된 realm을 가진다.

instanceof-realm-fail.js
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
 
const IframeError = iframe.contentWindow.Error;
const err = new IframeError('iframe에서 생성된 에러');
 
console.log(err instanceof Error);        // false — 다른 realm의 Error
console.log(err instanceof IframeError);  // true — 같은 realm이므로 통과
console.log(err.message);                 // "iframe에서 생성된 에러" — 에러 자체는 정상

비유하면 이렇다. 대한민국 여권으로 발급한 신분증을 미국 DMV에서 만든 신분증 인식기에 대면 인식이 안 된다. 발급 기관이 다르기 때문이다. 신분증 자체는 진짜인데, 검사 장비가 다른 나라 시스템을 모른다. instanceof가 하는 일이 정확히 이것이다 — 두 Error 생성자가 물리적으로 같은 객체인지 비교할 뿐, "이 값이 에러의 성질을 가지는가"를 묻는 게 아니다.


Error.isError()의 해결 방식

Error.isError()는 생성자 동일성 비교를 버리고, 에러 객체가 생성될 때 내부적으로 찍히는 브랜드(brand)를 확인한다.

[💡 잠깐! 이 용어는?] 브랜드 검사(Brand Check): 객체가 특정 내장 타입으로 생성됐을 때 엔진 내부에 기록되는 표식을 확인하는 방식이다. 프로토타입 체인이 아니라 객체 내부 슬롯을 보는 것이라, 프로토타입을 조작해도 속일 수 없다. Array.isArray()가 이미 같은 방식으로 동작한다.

error-iserror-basic.js
// realm을 가리지 않는다
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
 
const err = new iframe.contentWindow.Error('Oops!');
 
console.log(err instanceof Error);  // false
console.log(Error.isError(err));    // true
 
// 기본 동작
console.log(Error.isError(new Error('일반 에러')));              // true
console.log(Error.isError(new TypeError('타입 에러')));          // true
console.log(Error.isError(new RangeError('범위 에러')));         // true
 
// false 케이스
console.log(Error.isError('문자열'));                            // false
console.log(Error.isError({ message: '에러처럼 생긴 객체' }));    // false
console.log(Error.isError(null));                               // false

Array.isArray()가 realm을 가리지 않고 배열을 판별하는 것과 정확히 같은 원리다. 어느 realm에서 만든 배열이든 Array.isArray()true를 돌려준다. Error.isError()도 마찬가지다.


true / false 동작표

어떤 값이 true를 반환하고 어떤 값이 false를 반환하는지 정리한다.

입력값결과이유
new Error('msg')true내장 Error 브랜드 보유
new TypeError('msg')trueError 서브클래스, 브랜드 상속
new RangeError('msg')trueError 서브클래스, 브랜드 상속
new DOMException('msg')true브라우저 환경에서만
class CustomError extends Error {} 인스턴스trueextends Error로 브랜드 획득
iframe.contentWindow.Error 인스턴스truerealm 무관, 브랜드만 확인
'문자열'false원시값
{ message: '가짜 에러' }false브랜드 없는 일반 객체
Object.create(Error.prototype)false브랜드 없이 프로토타입만 연결
null, undefinedfalse원시값

Object.create(Error.prototype)false인 점이 핵심이다. 프로토타입 체인을 수동으로 이어 붙여도 브랜드가 없으면 통과하지 못한다. 반대로 instanceof는 이 경우 true를 반환한다. Error.isError()instanceof보다 더 엄격하다.

error-iserror-strict.js
const fakeError = Object.create(Error.prototype);
fakeError.message = '나는 에러처럼 보인다';
 
console.log(fakeError instanceof Error);  // true — 프로토타입 체인 통과
console.log(Error.isError(fakeError));    // false — 브랜드 없음
 
// 커스텀 에러는 통과한다
class CustomError extends Error {}
const custom = new CustomError('커스텀 에러');
 
console.log(custom instanceof Error);    // true
console.log(Error.isError(custom));      // true

실전 사용 사례

전역 에러 핸들러

global-error-handler.js
window.addEventListener('unhandledrejection', (event) => {
  const reason = event.reason;
 
  if (Error.isError(reason)) {
    reportError({
      type: reason.name,
      message: reason.message,
      stack: reason.stack,
    });
  } else {
    reportError({
      type: 'UnknownRejection',
      message: String(reason),
    });
  }
});

reason instanceof Error로 했다면, 다른 realm에서 온 에러를 잘못 분류하는 상황이 생긴다. 모니터링 도구에 올라온 에러 리포트가 엉뚱하게 분류된 적이 있다면 이 문제일 가능성이 있다.

Web Worker 경계

worker-error-boundary.js
const worker = new Worker('worker.js');
 
worker.addEventListener('message', (event) => {
  const { error } = event.data;
 
  if (Error.isError(error)) {
    handleError(error);
  }
});

폴리필

브라우저 지원이 아직 고르지 않으므로, 당장 프로덕션에서 쓰려면 폴리필을 작성해두는 것이 안전하다.

error-iserror-polyfill.js
function isError(value) {
  if (typeof Error.isError === 'function') {
    return Error.isError(value);
  }
  return value instanceof Error;
}
 
console.log(isError(new TypeError('bad')));  // true
console.log(isError('not an error'));        // false

브라우저 및 런타임 지원 현황

환경지원 버전상태
Chrome134+정식 지원
Edge134+정식 지원
Firefox138+정식 지원
Safari18.4부분 지원
Node.js24.3+정식 지원
Bun미지원미정
Deno미지원미정
node-vm-realm.js
const vm = require('vm');
 
const err = vm.runInNewContext('new Error("vm에서 생성된 에러")');
 
console.log(err instanceof Error);  // false — 다른 realm
console.log(Error.isError(err));    // true (Node.js 24.3+)

정리

  • instanceof Error는 같은 realm 안에서만 신뢰할 수 있다. iframe, Worker, vm, 익스텐션 경계를 넘으면 false를 돌려준다
  • Error.isError()는 객체 내부 브랜드를 확인하므로 realm에 무관하게 정확하게 동작한다
  • Object.create(Error.prototype)처럼 프로토타입만 이어 붙인 가짜 에러는 false를 반환해 instanceof보다 더 엄격하다
  • 커스텀 에러(class MyError extends Error {})는 true를 반환한다
  • Chrome 134+, Firefox 138+, Node.js 24.3+에서 정식 지원한다. 프로덕션 전환 전에는 폴리필을 준비해두는 것이 안전하다

참고:

관심 있을 만한 포스트

JavaScript using 키워드 — try/finally 없이 리소스를 자동으로 정리하는 법

TC39 Stage 4에 도달한 Explicit Resource Management 제안을 통해 using 키워드와 Symbol.dispose의 동작 원리를 살펴본다.

JavaScriptTC39

Temporal API — JavaScript Date의 30년 묵은 저주가 풀린다

Chrome 144가 Temporal API를 정식 탑재하면서 JavaScript 날짜 처리의 새 시대가 열렸다.

TemporalJavaScript

Native JSON Modules — 번들러 없이 JSON을 import하는 시대

Import Attributes와 함께 표준이 된 native JSON module. 어떻게 동작하고, 기존 번들러 방식과 뭐가 다른지 정리했다.

JavaScriptESM

Babel 7.29.0 — 10년 역사의 마지막 마이너, 그리고 8 RC1

2026년 1월 31일, Babel 7의 마지막 마이너 릴리스가 공개됐다. 이 버전이 갖는 역사적 의미와 Babel 8 RC1의 핵심 변화를 정리한다.

BabelJavaScript

V8의 Sea of Nodes 탈출기 — 왜 우아한 이론이 실전에서 무너졌는가

V8 팀이 10년간 사용한 Sea of Nodes IR을 포기하고 Turboshaft로 전환한 7가지 이유와 그 교훈을 정리한다.

V8컴파일러

jQuery 4.0 — 10년 만의 메이저 릴리스, 무엇이 바뀌었나

jQuery가 20주년을 맞아 10년 만에 메이저 버전을 출시했다. IE 지원 축소, ES 모듈 전환, Trusted Types 등 핵심 변경 사항을 정리한다.

jQueryJavaScript

V8 Mutable Heap Numbers — 숫자 하나 바꿀 때마다 새 객체를 만들던 비효율을 잡다

V8 엔진이 스크립트 컨텍스트의 숫자 변수를 매번 새 HeapNumber로 할당하던 방식을 제자리 수정(mutable)으로 바꿔 최대 2.5배 성능 향상을 달성했다.

V8JavaScript

V8 Explicit Compile Hints — 주석 한 줄로 JavaScript 시작 속도를 630ms 줄이는 법

Chrome 136에 도입된 V8의 Explicit Compile Hints 기능으로 JavaScript 초기 로딩 성능을 개선하는 원리와 사용법을 분석한다.

V8성능 최적화