Error が持っているもの、throw できるもの、finally が必ず動くこと、JSON にすると空になる理由、 cause で原因を繋ぐ書き方、独自のエラーを作って種類で分ける方法までを実行して確かめます。
Error が持っているのは、種類・文言・スタックトレースの3つです。
example.js
const error = new Error('壊れた');
console.log(error.name);
console.log(error.message);
console.log(String(error));
console.log(typeof error.stack);
出力書き換えて実行できます
Error
壊れた
Error: 壊れた
string
example.js
for (const Kind of [TypeError, RangeError, SyntaxError, ReferenceError]) {
const error = new Kind('x');
console.log(error.name, error instanceof Error);
}
出力書き換えて実行できます
TypeError true
RangeError true
SyntaxError true
ReferenceError true
name と message を : で繋いだものが、文字列にしたときの形です。
組み込みの種類
example.js
for (const value of [new Error('e'), '文字列', 42]) {
try {
throw value;
} catch (caught) {
console.log(typeof caught, caught instanceof Error);
}
}
出力書き換えて実行できます
object true
string false
number false
example.js
function risky(useError) {
throw useError ? new Error('理由が分かる') : '理由だけ';
}
for (const useError of [true, false]) {
try {
risky(useError);
} catch (error) {
console.log(error?.message ?? '(message が無い)', '|', typeof error?.stack);
}
}
出力書き換えて実行できます
理由が分かる | string
(message が無い) | undefined
すべて Error を継承しています。 だから instanceof Error で一括して受けられます。
よく出る2つは、それぞれ独立したページで扱っています。
→ TypeError: x is not a function
→ SyntaxError: Unexpected token
| 種類 |
いつ出るか |
TypeError |
型が合わない(関数でないものを呼んだ、null の中身を読んだ) |
RangeError |
値が範囲の外(配列の長さ、toFixed の桁) |
ReferenceError |
名前が見つからない |
SyntaxError |
構文として読めない(JSON.parse を含む) |
throw できるものは何でも
example.js
function run(fail) {
try {
if (fail) throw new Error('だめ');
return '成功';
} catch (error) {
return '失敗';
} finally {
console.log('finally');
}
}
console.log(run(false));
console.log(run(true));
出力書き換えて実行できます
finally
成功
finally
失敗
example.js
try {
JSON.parse('x');
} catch {
console.log('中身を見ないなら省略できる');
}
出力書き換えて実行できます
中身を見ないなら省略できる
文字列も数値も投げられます。ですが投げないでください。
Error でないものを投げると、message も stack も付きません。
受け取った側は何が起きたか調べられません。
javascript
const error = new Error('壊れた');
console.log(JSON.stringify(error));
console.log(Object.keys(error));
{}
[]
example.js
function toLog(error) {
return {
name: error.name,
message: error.message,
stack: typeof error.stack === 'string' ? error.stack.split('\n')[0] : null,
};
}
console.log(JSON.stringify(toLog(new Error('壊れた'))));
出力書き換えて実行できます
{"name":"Error","message":"壊れた","stack":"Error: 壊れた"}
finally は必ず動く
example.js
const low = new Error('通信できない');
const high = new Error('ユーザーの取得に失敗', { cause: low });
console.log(high.message);
console.log(high.cause.message);
出力書き換えて実行できます
ユーザーの取得に失敗
通信できない
javascript
try {
JSON.parse('x');
} catch (error) {
throw new Error('設定を読めません');
}
return より後に動きます。 後始末(閉じる・解放する・止める)を書く場所です。
finally の中で return すると、try や catch の結果を上書きします。
書かないでください。
使わない引数は省ける
example.js
class NotFoundError extends Error {
constructor(id) {
super(`見つかりません: ${id}`);
this.name = 'NotFoundError';
this.id = id;
}
}
const error = new NotFoundError(7);
console.log(error.name, error.message, error.id);
console.log(error instanceof NotFoundError, error instanceof Error);
出力書き換えて実行できます
NotFoundError 見つかりません: 7 7
true true
example.js
class NotFoundError extends Error {
constructor(id) {
super(`見つかりません: ${id}`);
this.name = 'NotFoundError';
}
}
function handle(error) {
if (error instanceof NotFoundError) return '404 として扱う';
if (error instanceof TypeError) return 'こちらの不具合';
return '不明';
}
console.log(handle(new NotFoundError(1)));
console.log(handle(new TypeError('x')));
console.log(handle(new Error('x')));
出力書き換えて実行できます
404 として扱う
こちらの不具合
不明
JSON にすると空になる
これがいちばん事故になります。
javascript
try {
JSON.parse('x');
} catch {
// 何もしない
}
example.js
function parse(text) {
try {
return JSON.parse(text);
} catch (error) {
if (error instanceof SyntaxError) return null;
throw error;
}
}
console.log(parse('{"a":1}'));
console.log(parse('x'));
出力書き換えて実行できます
{ a: 1 }
null
name も message も stack も、列挙されないので消えます。
ログに送ったつもりで {} だけが残る、というのはこれが原因です。
自分で取り出してください。
→ JSON.parse() と JSON.stringify()
原因を繋ぐ
cause で、元の失敗を保ったまま包み直せます(ES2022)。
握りつぶさずに、文脈だけを足せます。
これだと元の SyntaxError が消えます。cause を付けてください。
独自のエラーを作る
種類で分けたいときに作ります。
super(message) を呼び、name を自分で入れます。
→ class — 作る・継承する・隠す
受け取る側は種類で分けられます。
文言で分岐しないでください。 文言は処理系や版で変わります。
種類(instanceof)か、自分で付けた印(code など)で分けます。
よくある間違い
何でも catch して握りつぶす
問題が消えたのではなく、見えなくなっただけです。
記録するか、包んで投げ直すか、どちらかにしてください。
想定外まで受けてしまう
受けたい種類だけ受け、それ以外は投げ直す。
そうしないと、想定外の不具合まで null に化けます。
失敗したときにやり直す形は、こちらにまとめています。
→ 失敗したら再試行する
try の範囲が広すぎる
try に何十行も入れると、どこで失敗したのか分からなくなります。
落ちうる1行だけを囲んでください。
まとめ
Error が持つのは種類・文言・スタックトレース
- 組み込みの種類はすべて
Error を継承している
Error 以外を投げない。 message も stack も付かない
finally は return より後に動く。中で return しない
JSON.stringify(error) は {}。 自分で取り出す
- 包み直すときは
cause で元を残す
- 分岐は文言ではなく種類で