JavaScript 焦らずプログラミング (ES2022 版)
この本をサポートしてください: 購入 または 寄付
(広告です、ブロックしないでください。)

24 例外処理



この章では、JavaScript が例外をどのように処理するかを説明します。

  なぜ JavaScript はもっと頻繁に例外をスローしないのですか?

JavaScript は ES3 まで例外をサポートしていませんでした。そのため、言語とその標準ライブラリでは控えめに使用されています。

24.1 動機: 例外のスローとキャッチ

次のコードを考えてみましょう。ファイルに保存されたプロファイルを、クラス Profile のインスタンスを持つ配列に読み込みます。

function readProfiles(filePaths) {
  const profiles = [];
  for (const filePath of filePaths) {
    try {
      const profile = readOneProfile(filePath);
      profiles.push(profile);
    } catch (err) { // (A)
      console.log('Error in: '+filePath, err);
    }
  }
}
function readOneProfile(filePath) {
  const profile = new Profile();
  const file = openFile(filePath);
  // ··· (Read the data in `file` into `profile`)
  return profile;
}
function openFile(filePath) {
  if (!fs.existsSync(filePath)) {
    throw new Error('Could not find file '+filePath); // (B)
  }
  // ··· (Open the file whose path is `filePath`)
}

B 行で何が起こるかを見てみましょう。エラーが発生しましたが、問題を処理するのに最適な場所は現在の場所ではなく、A 行です。そこで、現在のファイルをスキップして次のファイルに進むことができます。

したがって

スローすると、次の構文がアクティブになります。

readProfiles(···)
  for (const filePath of filePaths)
    try
      readOneProfile(···)
        openFile(···)
          if (!fs.existsSync(filePath))
            throw

throw は、try 文に遭遇するまで、ネストされた構文を 1 つずつ終了します。実行は、その try 文の catch 句で続行されます。

24.2 throw

これは throw 文の構文です。

throw «value»;

24.2.1 どのような値をスローすべきか?

JavaScript では任意の値をスローできます。ただし、スタックトレースやエラーチェインなどの追加機能をサポートしているため、Error またはそのサブクラスのインスタンスを使用するのが最適です(§24.4 “Error およびそのサブクラス”を参照)。

これにより、次のオプションが残ります。

24.3 try

try 文の最大バージョンは次のようになります。

try {
  «try_statements»
} catch (error) {
  «catch_statements»
} finally {
  «finally_statements»
}

これらの句は、次のように組み合わせることができます。

24.3.1 try ブロック

try ブロックは、文の本体と見なすことができます。ここで、通常のコードを実行します。

24.3.2 catch

例外が try ブロックに到達した場合、その例外は catch 句のパラメータに割り当てられ、その句内のコードが実行されます。次に、実行は通常、try 文の後で続行されます。次の場合は、変更される可能性があります。

次のコードは、A 行でスローされた値が実際に B 行でキャッチされることを示しています。

const errorObject = new Error();
function func() {
  throw errorObject; // (A)
}

try {
  func();
} catch (err) { // (B)
  assert.equal(err, errorObject);
}
24.3.2.1 catch バインディングの省略 [ES2019]

スローされた値に関心がない場合は、catch パラメータを省略できます。

try {
  // ···
} catch {
  // ···
}

これは時々役立つことがあります。たとえば、Node.js には、func 内でエラーがスローされるかどうかを確認する API 関数 assert.throws(func) があります。これは次のように実装できます。

function throws(func) {
  try {
    func();
  } catch {
    return; // everything OK
  }
  throw new Error('Function didn’t throw an exception!');
}

ただし、この関数のより完全な実装には、catch パラメータがあり、たとえば、その型が期待どおりであるかどうかを確認します。

24.3.3 finally

finally 句内のコードは、try ブロックまたは catch 句で何が起こっても、try 文の最後に常に実行されます。

finally の一般的なユースケースを見てみましょう。リソースを作成し、そのリソースの使用が完了したら、そのリソースを常に破棄したいとします。その使用中に何が起こっても構いません。これは次のように実装します。

const resource = createResource();
try {
  // Work with `resource`. Errors may be thrown.
} finally {
  resource.destroy();
}
24.3.3.1 finally は常に実行される

finally 句は、エラーがスローされた場合でも(A 行)常に実行されます。

let finallyWasExecuted = false;
assert.throws(
  () => {
    try {
      throw new Error(); // (A)
    } finally {
      finallyWasExecuted = true;
    }
  },
  Error
);
assert.equal(finallyWasExecuted, true);

そして、return 文がある場合でも(A 行)

let finallyWasExecuted = false;
function func() {
  try {
    return; // (A)
  } finally {
    finallyWasExecuted = true;
  }
}
func();
assert.equal(finallyWasExecuted, true);

24.4 Error およびそのサブクラス

Error は、すべての組み込みエラークラスの共通のスーパー クラスです。

24.4.1 クラス Error

これは、Error のインスタンスプロパティとコンストラクターがどのように見えるかです。

class Error {
  // Instance properties
  message: string;
  cause?: any; // ES2022
  stack: string; // non-standard but widely supported

  constructor(
    message: string = '',
    options?: ErrorOptions // ES2022
  );
}
interface ErrorOptions {
  cause?: any; // ES2022
}

コンストラクターには 2 つのパラメータがあります。

次のサブセクションでは、インスタンスプロパティ .message.cause、および .stack について詳しく説明します。

24.4.1.1 Error.prototype.name

各組み込みエラークラス E には、プロパティ E.prototype.name があります。

> Error.prototype.name
'Error'
> RangeError.prototype.name
'RangeError'

したがって、組み込みエラーオブジェクトのクラス名を取得する方法は 2 つあります。

> new RangeError().name
'RangeError'
> new RangeError().constructor.name
'RangeError'
24.4.1.2 エラーインスタンスプロパティ .message

.message にはエラーメッセージのみが含まれます。

const err = new Error('Hello!');
assert.equal(String(err), 'Error: Hello!');
assert.equal(err.message, 'Hello!');

メッセージを省略すると、空の文字列がデフォルト値として使用されます(Error.prototype.message から継承されます)。

メッセージを省略すると、空の文字列になります。

assert.equal(new Error().message, '');
24.4.1.3 エラーインスタンスプロパティ .stack

インスタンスプロパティ .stack は ECMAScript の機能ではありませんが、JavaScript エンジンで広くサポートされています。通常は文字列ですが、その正確な構造は標準化されておらず、エンジンによって異なります。

これは JavaScript エンジン V8 での表示例です。

const err = new Error('Hello!');
assert.equal(
err.stack,
`
Error: Hello!
    at file://ch_exception-handling.mjs:1:13
`.trim());
24.4.1.4 エラーインスタンスプロパティ .cause [ES2022]

インスタンスプロパティ .cause は、new Error() の 2 番目のパラメータの options オブジェクトを介して作成されます。これにより、現在のエラーの原因となった他のエラーを指定できます。

const err = new Error('msg', {cause: 'the cause'});
assert.equal(err.cause, 'the cause');

このプロパティの使用方法については、§24.5 “エラーのチェイン”を参照してください。

24.4.2 Error の組み込みサブクラス

Error には、次のサブクラスがあります – ECMAScript 仕様から引用します。

24.4.3 Error のサブクラス化

ECMAScript 2022 以降、Error コンストラクターは 2 つのパラメータを受け入れます(前のサブセクションを参照)。したがって、それをサブクラス化する際には、2 つの選択肢があります。サブクラスでコンストラクターを省略するか、次のように super() を呼び出すかです。

class MyCustomError extends Error {
  constructor(message, options) {
    super(message, options);
    // ···
  }
}

24.5 エラーのチェイン

24.5.1 なぜエラーをチェインしたいのか?

場合によっては、より深くネストされた関数呼び出し中にスローされたエラーをキャッチし、それに追加情報を添付したい場合があります。

function readFiles(filePaths) {
  return filePaths.map(
    (filePath) => {
      try {
        const text = readText(filePath);
        const json = JSON.parse(text);
        return processJson(json);
      } catch (error) {
        // (A)
      }
    });
}

try 句内のステートメントは、あらゆる種類のエラーをスローする可能性があります。ほとんどの場合、エラーはエラーの原因となったファイルのパスを認識しません。そのため、A 行でその情報を添付したいのです。

24.5.2 error.cause によるエラーのチェイン [ES2022]

ECMAScript 2022 以降、new Error() では、その原因を指定できます。

function readFiles(filePaths) {
  return filePaths.map(
    (filePath) => {
      try {
        // ···
      } catch (error) {
        throw new Error(
          `While processing ${filePath}`,
          {cause: error}
        );
      }
    });
}

24.5.3 .cause の代替: カスタムエラークラス

次のカスタムエラークラスは、チェインをサポートしています。.cause と前方互換性があります。

/**
 * An error class that supports error chaining.
 * If there is built-in support for .cause, it uses it.
 * Otherwise, it creates this property itself.
 *
 * @see https://github.com/tc39/proposal-error-cause
 */
class CausedError extends Error {
  constructor(message, options) {
    super(message, options);
    if (
      (isObject(options) && 'cause' in options)
      && !('cause' in this)
    ) {
      // .cause was specified but the superconstructor
      // did not create an instance property.
      const cause = options.cause;
      this.cause = cause;
      if ('stack' in cause) {
        this.stack = this.stack + '\nCAUSE: ' + cause.stack;
      }
    }
  }
}

function isObject(value) {
  return value !== null && typeof value === 'object';
}

  演習: 例外処理

exercises/exception-handling/call_function_test.mjs

  クイズ

クイズアプリを参照してください。