DanLevy.net

クイズ: 高度なJSエラーマスター

例外は本当に例外的ですか?

JavaScript のエラー、全部把握してると思う?

JSON.stringify(error) は何を返す?

const error = new Error('Oops');
console.log(JSON.stringify(error));

Error オブジェクトは列挙できないプロパティ(messagenamestack)を持つため、JSON.stringify(){} を返します。これは API 応答でエラーを送る際の典型的な落とし穴です。JSON.stringify(error, Object.getOwnPropertyNames(error)) を使うか、代わりにプレーンオブジェクトを作成してください。

この二つの違いは何ですか?

const err = new Error('Test');
console.log(err);
console.log(JSON.stringify(err));

console.log(err) はエラーメッセージとスタックトレースを表示する。コンソールは Error オブジェクトを特別に扱うからだ。JSON.stringify(err)'{}' を返す。Error のプロパティは列挙可能ではないためだ。この違いで API デバッグ中に多くの開発者がつまずく。

これらのチェックの結果は?

class CustomError extends Error {}
const err = new CustomError('test');
console.log(err instanceof CustomError);
console.log(err instanceof Error);
console.log(err instanceof Object);

すべてのチェックは true を返します。CustomErrorError を継承し、ErrorObject を継承しています。instanceof 演算子はプロトタイプチェーン全体を確認するので、CustomError のインスタンスは ErrorObject のインスタンスでもあります。

iframe 間で instanceof Error はどうなる?

// In iframe:
const iframeError = new Error('test');
// In parent window:
console.log(iframeError instanceof Error);

instanceof は異なる実行コンテキスト(iframe や worker)間では false を返すことがあります。各コンテキストは独自の Error コンストラクタを持っているためです。コンテキストを超えて確実にエラーを判定したい場合は Object.prototype.toString.call(obj) === '[object Error]' を使いましょう。

文字列をthrowしたらどうなる?

try {
throw "Oops!";
} catch (e) {
console.log(e instanceof Error);
console.log(typeof e);
}

JavaScriptでは任意の値をthrowできる。ここでは e instanceof Errorfalse で、typeof e"string" になる。これにより、捕捉した例外がすべてErrorオブジェクトであると想定したエラーハンドリングコードが壊れる可能性がある。デバッグしやすくするため、常にErrorインスタンスをthrowしよう。

err.name の値は何ですか?

class CustomError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
const err = new CustomError('test');
console.log(err.name);

err.name"CustomError" です。なぜなら this.constructor.name がクラス名を返すからです。this.name = this.constructor.name を設定するのは、カスタムエラークラスがスタックトレースやエラーメッセージで正しい名前を表示するようにする一般的なパターンです。

name を設定しない場合の出力は?

class MyError extends Error {
// No constructor or name setting
}
const err = new MyError('test');
console.log(err.name);

this.name を明示的に設定しないと、エラーは Error クラスからデフォルトの name プロパティ("Error")を継承します。だからカスタムエラークラスではコンストラクタ内で必ず this.name = this.constructor.name を設定すべきです。

wrapper.cause.message は何を返す?

const original = new Error('Original error');
const wrapper = new Error('Wrapper',
{ cause: original }
);
console.log(wrapper.cause.message);

Error.cause(ES2022)はエラーをチェーンさせ、元のエラーコンテキストを保持できる。wrapper.cause は元のエラーを指すので、wrapper.cause.message"Original error" を返す。これは下位レベルのエラーを上位コンテキストでラップする際に便利だ。

Error.captureStackTrace は何をしますか?

function createError(msg) {
const err = new Error(msg);
Error.captureStackTrace(err, createError);
return err;
}
const error = createError('test');

Error.captureStackTrace(V8/Node.js)は、指定した関数(createError)をスタックトレースから除外し、エラーファクトリ関数をエンドユーザーに見えなくします。これにより、ファクトリが呼び出された場所を指す、よりクリーンなスタックトレースが得られます。

エラーメッセージは何ですか?

function validate(value) {
if (!value) {
throw new Error(
`Value ${value} is invalid`
);
}
}
try {
validate(undefined);
} catch (e) {
console.log(e.message);
}

テンプレートリテラルは補間時に undefined を文字列 "undefined" に変換します。エラーメッセージは "Value undefined is invalid" になります。よりクリーンなメッセージにするには、補間前に value ?? 'null' などのチェックを検討してください。

クライアントに何が送られる?

// Express.js route
app.get('/api/data', (req, res) => {
const error = new Error('Database failed');
res.json({ error });
});

res.json() は内部で JSON.stringify() を使用するため、Error オブジェクトは {} になります。クライアントは {"error":{}} を受け取ります。これを修正するには、res.json({ error: error.message }) または res.json({ error: { message: error.message, name: error.name } }) を使用します。

Promise.reject() は何を受け取れる?

Promise.reject('string').catch(e =>
console.log(typeof e)
);
Promise.reject({code: 404}).catch(e =>
console.log(e.code)
);
Promise.reject(42).catch(e =>
console.log(e)
);

throw と同様に、Promise.reject() は任意の値(文字列、オブジェクト、数値など)を受け取れます。これにより "string"40442 が出力されます。Promise チェーンで捕捉した値の型は常に確認しましょう。特にサードパーティのコードが Error 以外で拒否する可能性がある場合は注意が必要です。

error.codeerror.errno の信頼性はどれくらいですか?

const fs = require('fs');
fs.readFile('missing.txt', (err, data) => {
if (err) {
console.log(err.code); // 'ENOENT'
console.log(err.errno); // -2
}
});

codeerrno のようなプロパティは環境固有(この場合は Node.js)で、標準の Error オブジェクトには含まれません。ブラウザのエラーにはこれらのプロパティはありません。常に存在を確認しましょう:if (err.code === 'ENOENT') のように、存在すると仮定しないでください。

これらのチェックは何を返す?

const fakeError = {
name: 'Error',
message: 'Fake error',
stack: 'fake stack'
};
console.log(fakeError instanceof Error);
console.log(Object.prototype.toString.call(
fakeError
) === '[object Error]');

instanceof Error はオブジェクトが Error コンストラクタで作られていないため false を返す。Object.prototype.toString.call()false を返す(実際は '[object Object]' が返る)ので、内部の [[Class]] スロットをチェックしている。両方の手法ともこの偽エラーオブジェクトを正しく判別している。

エラーハンドリングの極意

シリアライズの落とし穴からクロスコンテキストの instanceof 失敗まで、こういった高度な概念がジュニア開発者と、​すでに​傷だらけの​ベテラン​を分けます。

もっと挑戦したいですか? 追加の JavaScript、アルゴリズム、その他の脳トレが揃った 全クイズコレクション をチェックしてください。