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을 가진다.
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()가 이미 같은 방식으로 동작한다.
// 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)); // falseArray.isArray()가 realm을 가리지 않고 배열을 판별하는 것과 정확히 같은 원리다. 어느 realm에서 만든 배열이든 Array.isArray()는 true를 돌려준다. Error.isError()도 마찬가지다.
true / false 동작표
어떤 값이 true를 반환하고 어떤 값이 false를 반환하는지 정리한다.
| 입력값 | 결과 | 이유 |
|---|---|---|
new Error('msg') | true | 내장 Error 브랜드 보유 |
new TypeError('msg') | true | Error 서브클래스, 브랜드 상속 |
new RangeError('msg') | true | Error 서브클래스, 브랜드 상속 |
new DOMException('msg') | true | 브라우저 환경에서만 |
class CustomError extends Error {} 인스턴스 | true | extends Error로 브랜드 획득 |
iframe.contentWindow.Error 인스턴스 | true | realm 무관, 브랜드만 확인 |
'문자열' | false | 원시값 |
{ message: '가짜 에러' } | false | 브랜드 없는 일반 객체 |
Object.create(Error.prototype) | false | 브랜드 없이 프로토타입만 연결 |
null, undefined | false | 원시값 |
Object.create(Error.prototype)이 false인 점이 핵심이다. 프로토타입 체인을 수동으로 이어 붙여도 브랜드가 없으면 통과하지 못한다. 반대로 instanceof는 이 경우 true를 반환한다. Error.isError()가 instanceof보다 더 엄격하다.
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실전 사용 사례
전역 에러 핸들러
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 경계
const worker = new Worker('worker.js');
worker.addEventListener('message', (event) => {
const { error } = event.data;
if (Error.isError(error)) {
handleError(error);
}
});폴리필
브라우저 지원이 아직 고르지 않으므로, 당장 프로덕션에서 쓰려면 폴리필을 작성해두는 것이 안전하다.
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브라우저 및 런타임 지원 현황
| 환경 | 지원 버전 | 상태 |
|---|---|---|
| Chrome | 134+ | 정식 지원 |
| Edge | 134+ | 정식 지원 |
| Firefox | 138+ | 정식 지원 |
| Safari | 18.4 | 부분 지원 |
| Node.js | 24.3+ | 정식 지원 |
| Bun | 미지원 | 미정 |
| Deno | 미지원 | 미정 |
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+에서 정식 지원한다. 프로덕션 전환 전에는 폴리필을 준비해두는 것이 안전하다
참고:
- MDN Web Docs — Error.isError(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/isError
- TC39 Proposal — Error.isError: https://github.com/tc39/proposal-is-error
- Can I use — Error.isError: https://caniuse.com/mdn-javascript_builtins_error_iserror
관심 있을 만한 포스트
JavaScript using 키워드 — try/finally 없이 리소스를 자동으로 정리하는 법
TC39 Stage 4에 도달한 Explicit Resource Management 제안을 통해 using 키워드와 Symbol.dispose의 동작 원리를 살펴본다.
Temporal API — JavaScript Date의 30년 묵은 저주가 풀린다
Chrome 144가 Temporal API를 정식 탑재하면서 JavaScript 날짜 처리의 새 시대가 열렸다.
Native JSON Modules — 번들러 없이 JSON을 import하는 시대
Import Attributes와 함께 표준이 된 native JSON module. 어떻게 동작하고, 기존 번들러 방식과 뭐가 다른지 정리했다.
Babel 7.29.0 — 10년 역사의 마지막 마이너, 그리고 8 RC1
2026년 1월 31일, Babel 7의 마지막 마이너 릴리스가 공개됐다. 이 버전이 갖는 역사적 의미와 Babel 8 RC1의 핵심 변화를 정리한다.
V8의 Sea of Nodes 탈출기 — 왜 우아한 이론이 실전에서 무너졌는가
V8 팀이 10년간 사용한 Sea of Nodes IR을 포기하고 Turboshaft로 전환한 7가지 이유와 그 교훈을 정리한다.
jQuery 4.0 — 10년 만의 메이저 릴리스, 무엇이 바뀌었나
jQuery가 20주년을 맞아 10년 만에 메이저 버전을 출시했다. IE 지원 축소, ES 모듈 전환, Trusted Types 등 핵심 변경 사항을 정리한다.
V8 Mutable Heap Numbers — 숫자 하나 바꿀 때마다 새 객체를 만들던 비효율을 잡다
V8 엔진이 스크립트 컨텍스트의 숫자 변수를 매번 새 HeapNumber로 할당하던 방식을 제자리 수정(mutable)으로 바꿔 최대 2.5배 성능 향상을 달성했다.
V8 Explicit Compile Hints — 주석 한 줄로 JavaScript 시작 속도를 630ms 줄이는 법
Chrome 136에 도입된 V8의 Explicit Compile Hints 기능으로 JavaScript 초기 로딩 성능을 개선하는 원리와 사용법을 분석한다.