JavaScriptでカスラムエラーをどう作るか
はじめに:IE 11、Firefox 28、Chrome 34で試している
JavaScriptのErrorオブジェクトにはstack
プロパティにコールスタックを表す文字列がセットされる。これは現行のECMAScript仕様では規定されておらず、実装によって違いがある。
function foo() { bar(); } function bar() { baz(); } function baz() { throw new Error('X'); } (function main() { try { foo(); } catch (error) { console.log(error.stack); } })();
Chromeでのコンソール出力の例。
Error: X at baz (http://localhost/CustomError.html:8:24) at bar (http://localhost/CustomError.html:7:18) at foo (http://localhost/CustomError.html:6:18) at main (http://localhost/CustomError.html:12:9) at http://localhost/CustomError.html:16:3
このように、ネイティブに用意されているエラーオブジェクト用のコンストラクタ(Error
, TypeError
, RangeError
など)を使う場合、stack
プロパティはどの実装でもセットされる。ただし、セットされるタイミングは少し異なり、Firefox・Chromeではオブジェクト作成時にセットされ、IEではthrow
されるときにセットされる。
var e = new Error('X'); console.log(e.stack); // IEではまだセットされていない try { throw e; } catch (e) { console.log(e.stack); // セットされる }
ネイティブに用意されているコンストラクタでは物足りない場合、Error
を継承した独自のエラーコンストラクタを作ることを思いつく。ところが、これだとIEでしかstack
プロパティがセットされない。
function TimeoutError(message) { Error.call(this, message); } TimeoutError.prototype = Object.create(Error.prototype, { constructor: Object.getOwnPropertyDescriptor(TimeoutError.prototype, 'constructor'), name: { value: 'TimeoutError', configurable: true, enumerable: true, writable: true } }); var e = new TimeoutError('session timeout'); try { throw e; } catch (e) { console.log(e.stack); // IEではコールスタックを表す文字列 // Firefoxでは空文字列 // Chromeではundefined }
IEではthrow
されるオブジェクトがError.prototype
を継承していればstack
プロパティがセットされるようだ。Firefox・Chromeはネイティブコンストラクタでのみstack
プロパティがセットされるように見える。
ではどうすればいいだろうか。コンストラクタを用意せず、継承もせず、こんなので十分だと思う。
var createError = (function () { var hasOwnProperty = Object.prototype.hasOwnProperty; return function createError(name, message, properties) { var e = new Error(message); e.name = name; if (properties) for (var key in properties) if (hasOwnProperty.call(properties, key)) e[key] = properties[key]; try { throw e; // for IE } catch (e) { return e; } }; })(); var timeoutError = createError('TimeoutError', 'session timeout'); var fetchError = createError('FetchError', '404 Not Found', { statusCode: 404 });
エラーオブジェクトを扱うときは、instanceof
ではなくname
プロパティからその種類を判別すればよい。ネイティブのエラーオブジェクトも同じように判別できる(ECMAScript仕様にて規定)。
new TypeError().name === 'TypeError'; fetch(url, { timeout: 10 * 1000 }).then(function (text) { // do something }, function (error) { switch (error.name) { case 'TimeoutError': alert('タイムアウトしました'); break; case 'FetchError': if (error.statusCode === 404) { alert('リソースが見つかりません'); break; } alert('リソースを取得できませんでした(' + error.statusCode + ')'); } });
createError
関数の分、コールスタックを一行消費してしまうのが惜しい。IE・Chromeでは、stack
プロパティのコールスタックは10行までのようだ。
(function () { function a() { b(); } function b() { c(); } function c() { d(); } function d() { e(); } function e() { f(); } function f() { g(); } function g() { h(); } function h() { i(); } function i() { j(); } function j() { k(); } function k() { l(); } function l() { throw new Error('HELLO?'); } try { a(); } catch (e) { console.log(e.stack); } })();
Chromeでのコンソール出力の例。
Error: HELLO? at l (http://localhost/CustomError.html:30:24) at k (http://localhost/CustomError.html:29:18) at j (http://localhost/CustomError.html:28:18) at i (http://localhost/CustomError.html:27:18) at h (http://localhost/CustomError.html:26:18) at g (http://localhost/CustomError.html:25:18) at f (http://localhost/CustomError.html:24:18) at e (http://localhost/CustomError.html:23:18) at d (http://localhost/CustomError.html:22:18) at c (http://localhost/CustomError.html:21:18)
ES6のObject.assign
があれば、それで十分かもしれない。
throw Object.assign(new Error('404 Not Found'), { name: 'FetchError', statusCode: 404 });