Error.isError() — realm을 넘나드는 안전한 에러 검사 API
instanceof Error가 iframe과 worker에서 실패하는 이유, 그리고 이를 근본적으로 해결하는 Error.isError()의 동작 원리를 정리한다.
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
비슷한 주제의 최신 글
태그가 겹치는 글
공통 태그가 많을수록 위에 보인다