【JavaScript】エラー処理 完全ガイド【保存版】

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/726562/6915be1b-8ea0-a2a0-2c48-cbbb0c01bf4c.png)

> **本記事は、Valentino Gagliardi 氏の "[A mostly complete guide to error handling in JavaScript.](https://www.valentinog.com/blog/error/)" を許可を頂いた上で翻訳したものです。**

## TOC

- [プログラミングにおけるエラーとは？](#プログラミングにおけるエラーとは)
- [JavaScript におけるエラーとは？](#javascript-におけるエラーとは)
- [JavaScript エラー型の種類](#javascript-エラー型の種類)
- [例外とは？](#例外とは)
- [例外を投げると何が起きる？](#例外を投げると何が起きる)
- [同期的エラー処理](#同期的エラー処理)
  - [通常関数のエラー処理](#通常関数のエラー処理)
  - [ジェネレーター関数のエラー処理](#ジェネレーター関数のエラー処理)
- [非同期エラー処理](#非同期エラー処理)
  - [タイマーのエラー処理](#タイマーのエラー処理)
  - [イベントのエラー処理](#イベントのエラー処理)
  - [onerror はどうだろう？](#onerror-はどうだろう)
  - [Promise を用いたエラー処理](#promise-を用いたエラー処理)
  - [プロミス（Promise）、エラー（error）そしてスロー（throw）](#プロミスpromiseエラーerrorそしてスローthrow)
  - ["プロミス化"されたタイマーのエラー処理](#プロミス化されたタイマーのエラー処理)
  - [Promise.all のエラー処理](#promiseall-のエラー処理)
  - [Promise.any のエラー処理](#promiseany-のエラー処理)
  - [Promise.race のエラー処理](#promiserace-のエラー処理)
  - [Promise.allSettled のエラー処理](#promiseallsettled-のエラー処理)
  - [async/await のエラー処理](#asyncawait-のエラー処理)
  - [非同期ジェネレーターのエラー処理](#非同期ジェネレーターのエラー処理)
- [Node.js のエラー処理](#nodejs-のエラー処理)
  - [Node.js の同期エラー処理](#nodejs-の同期エラー処理)
  - [Node.js の非同期エラー処理: コールバックパターン](#nodejs-の非同期エラー処理-コールバックパターン)
  - [Node.js における非同期エラー処理: イベントエミッター](#nodejs-における非同期エラー処理-イベントエミッター)
- [まとめ](#まとめ)

## プログラミングにおけるエラーとは？

私たちの書くプログラムは **常にうまく動作するわけではありません。**

時に、**プログラムを停止させたり、ユーザーに何か問題が起こったことを知らせたい** シチュエーションがあります。

例えば、以下のようなケースがあるでしょう:

- プログラムが存在しないファイルを開こうとした
- ネットワークの接続が不調である
- ユーザーが無効な値を入力した

すべてのケースで、私たちがプログラマーとして、またはプログラミングエンジンを通して、 **エラー** を作成します。

エラーを作成することで、ユーザーに問題が起きたことをメッセージで伝えたり、プログラムの実行を停止させたりできるのです。

## JavaScript におけるエラーとは？

**JavaScript におけるエラーはオブジェクト** です。このオブジェクトは、後にプログラムを停止するために **投げられる** ものです。

JavaScript で新しくエラーを作成するには、適切な **コンストラクタ関数** を呼び出します。例えば、一般的なエラーを新規に作成するには以下を実行します:

```js
const err = new Error("Something bad happened!");
```

`new` というキーワードを省略することもできます:

```js
const err = Error("Something bad happened!");
```

一度作成されると、エラーオブジェクトは3つのプロパティを提供します。

- `message`: エラーメッセージを含む文字列
- `name`: エラーのタイプ
- `stack`: 関数実行のスタックトレース

例えば、適当なメッセージ文字列で`TypeError` オブジェクトを作成した場合、`message` は実際に渡した文字列となり、`name` は`"TypeError"`となります:

```js
const wrongType = TypeError("Wrong type given, expected number");

wrongType.message; // "Wrong type given, expected number"
wrongType.name; // "TypeError"
```

Firefox は上記のプロパティの他に、`columnNumber`、`filename`、`lineNumber`といった非標準プロパティを実装しています。

## JavaScript エラー型の種類

JavaScript にはたくさんのエラー型があります。具体的には以下の通りです:

- `Error`
- `EvalError`
- `InternalError`
- `RangeError`
- `ReferenceError`
- `SyntaxError`
- `TypeError`
- `URIError`

これらのエラー型は、あたらしいエラーオブジェクトを返す **本物のコンストラクタ関数** であることを忘れないでください。

あなた自身のエラーオブジェクトを作成する際、`Error` と `TypeError` という最も一般的な 2 つのエラー型を使うことが多いでしょう。

エラーの大多数は `InternalError` や `SyntaxError` のように、JavaScript エンジンから直接的に発現するものがほとんどです。

`TypeError` の一例は、`const` に再代入しようとした際に発生します:

```js
const name = "Jules";
name = "Caty";

// TypeError: Assignment to constant variable.
```

`SyntaxError` の一例は、タイプミスをしたときに発生します:

```js
va x = '33';
// SyntaxError: Unexpected identifier
```

または、`await` を `async` 関数以外で利用するなど、予約語を不適切な場所を使った場合にも発生します:

```js
function wrong(){
    await 99;
}

wrong();

// SyntaxError: await is only valid in async function
```

`TypeError`の他の例としては、ページに存在しない HTML 要素を指定したときに発生します:

```text
Uncaught TypeError: button is null
```

これらのよくあるエラーオブジェクトに加えて、`AggregateError` オブジェクトが JavaScript にもうすぐ導入される予定です。後ほど見るように、`AggregateError` は複数のエラーをまとめる際に便利です。

これらの組み込みエラーに加えて、ブラウザでは以下のようなもの目にすることがあります:

- `DOMException`
- `DOMError` (Dupulicated, 今は使われていない)

`DOMException` は Web APIs に関連するエラーファミリーです。ブラウザの中で、ばかげたことをしたときに投げられます。例えば以下のようなことです:

```js
document.body.appendChild(document.cloneNode(true));
```

結果:

```text
Uncaught DOMException: Node.appendChild: May not add a Document as a child
```

完全なリストは、[MDNのこちらのページ](https://developer.mozilla.org/ja/docs/Web/API/DOMException)を参照してください。

## 例外とは？

多くのデベロッパーは、エラーと例外を同様のものとして考えています。実際には、 **エラーオブジェクトが投げられたときにのみ、エラーオブジェクトが例外になる** のです。

JavaScript で例外を投げるには、`throw`とエラーオブジェクトを用います:

```js
const wrongType = TypeError("Wrong type given, expected number");

throw wrongType;
```

短縮形のほうがより一般的です。多くのコードベースで以下のようなものを目にするでしょう:

```js
throw TypeError("Wrong type given, expected number");
```

または

```js
throw new TypeError("Wrong type given, expected number");
```

関数や条件分岐構文の外で例外を投げることはほとんどありません。代わりに、以下の例を考えてみましょう:

```js
function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}
```

ここでは、関数の引数が文字列(string)かどうかをチェックしています。文字列でなければ、例外を投げます。

JavaScript のルール的には、エラーオブジェクトだけではなく何でも投げることができます:

```js
throw Symbol();
throw 33;
throw "Error!";
throw null;
```

しかしながら、 **プリミティブ型を投げることは避け、適切はエラーオブジェクトを投げるべき** です。

そうすることで、コードベースにおいてエラー処理の一貫性を保つことができます。他のチームメンバーがエラーオブジェクトにおいて `error.message` や `error.stack` にアクセスすることができます。

## 例外を投げると何が起きる？

**例外はエレベーターが上に行くようなものです。** 一度例外を投げると、どこかで止められない限りプログラムスタックの中でぶくぶくと泡立ってしまします。

以下のようなコードを考えてみましょう:

```js
function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);
```

このコードをブラウザもしくは Node.js で実行した場合、プログラムは停止し以下のようなエラーを表示します:

```text
Uncaught TypeError: Wrong type given, expected a string
    toUppercase http://localhost:5000/index.js:3
    <anonymous> http://localhost:5000/index.js:9
```

さらに、エラーが発生した正確な行数を把握することができます。

この表示が **スタックトレース** であり、プログラムの問題を追跡する際に便利です。

スタックトレースは下から上に積み上がります。つまりここでは以下のようになっていました:

```text
    toUppercase http://localhost:5000/index.js:3
    <anonymous> http://localhost:5000/index.js:9
```

ここから以下のことが言えます:

- 9 行目にあるプログラムの何かが `toUppercase` を呼び出した
- 3 行目において `toUppercase` で問題が発生した

ブラウザのコンソールで確認する以外にも、エラーオブジェクトの `stack` プロパティにアクセスすることによってスタックトレースを見ることができます。

もし例外が **キャッチされなかった** 場合、つまり、プログラマが例外をキャッチするために何もしなかった場合、プログラムはクラッシュします。

**コードの中で、いつ、どこで例外をキャッチするかは、その時々で異なります。**

例えば、 **プログラムを完全にクラッシュさせるために、例外をスタックに加えて伝播させたいかもしれません。** これは、無効なデータで処理を進めるよりもプログラムを停止させたほうが安全である、といった、致命的なエラーを処理する際に起こりうることです。

さて、ここまでで基本の紹介をしたので、 **JavaScript の同期処理と非同期処理における、エラーと例外処理** に話を進めましょう。

## 同期的エラー処理

同期処理のコードはほとんどの場合単純でわかりやすいので、エラー処理も簡単です。

### 通常関数のエラー処理

同期処理のコードは、書かれた通りに順番に実行されます。前述のコードをもう一度見てみましょう:

```js
function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);
```

ここで、JavaScript エンジンは `toUppercase` を呼び出して実行しています。すべての処理は **同期的** に行われます。このように同期関数から発生する例外を **キャッチ** するには、`try/catch/finally` を使うことができます:

```js
try {
  toUppercase(4);
} catch (error) {
  console.error(error.message);
  // or log remotely
} finally {
  // clean up
}
```

通常、`try` はハッピーパスや、潜在的に例外を投げる可能性のある関数呼び出しに対して利用します。

`catch` は、 **実際に例外を捉えます。** **エラーオブジェクトを受け取り** 、エラーの内容を検査することができます（そして本番環境ではログをリモートサーバーに送信したりします）。

一方で、`finally` ステートメントは、関数の実行結果に関わらず実行されます。つまり、関数が失敗したか成功したかにかかわらず `finally` 内に書かれたコードは実行されます。

`try/catch/finally` は **同期的な** 構造であることを覚えておいて下さい。そしていま、 **非同期処理のコードから発生する例外をキャッチする方法を獲得した** のです。

### ジェネレーター関数のエラー処理

JavaScript におけるジェネレーター関数は、関数の特殊な形式です。

この形式の関数は、関数の内側のスコープとその外側の間で **双方向のコミュニケーションチャネル** を提供する以外に、 **任意に停止したり再開したり** することができます。

ジェネレーター関数を作成するには、`function` キーワードの後ろにアスタリスク `*` を付けます:

```js
function* generate() {
//
}
```

そうすると、値を返すために関数内で `yield` を使用することができます:

```js
function* generate() {
  yield 33;
  yield 99;
}
```

**ジェネレーター関数の返り値** は **イテレータオブジェクト** です。ジェネレーターから値を取り出すためには、2つの方法があります:

- イテレータオブジェクトの `next()` を呼び出す
- `for...of` で **イテレーション** する

先程の例で、ジェネレーターから値を取り出す場合は、以下のようにできます:

```js
function* generate() {
  yield 33;
  yield 99;
}

const go = generate();
```

ここで `go` がイテレータオブジェクトになります。

ここから、`go.next()` を呼び出し、実行を進めることができます:

```js
function* generate() {
  yield 33;
  yield 99;
}

const go = generate();

const firstStep = go.next().value; // 33
const secondStep = go.next().value; // 99
```

ジェネレーターは、 **呼び出し元から値や例外を受け取ることもできます。**

`next()` に加えて、ジェネレーターから返されたイテレータオブジェクトは、`throw()` メソッドを持っています。

このメソッドを利用して、ジェネレーターに例外を注入することによってプログラムを停止させてみましょう:

```js
function* generate() {
  yield 33;
  yield 99;
}

const go = generate();

const firstStep = go.next().value; // 33

go.throw(Error("Tired of iterating!"));

const secondStep = go.next().value; // never reached
```

注入された例外をキャッチするには、ジェネレーター関数内の処理を `try/catch` 構文で囲む必要があります（必要であれば `finally` も利用できます）:

```js
function* generate() {
  try {
    yield 33;
    yield 99;
  } catch (error) {
    console.error(error.message);
  }
}
```

ジェネレーター関数は例外を関数の外に投げることもできます。この仕組みは、`try/catch/finally` を使って同期処理の例外をキャッチするものと同じです。

ジェネレーター関数に対して `for...of` 構文を利用する例は以下のとおりです:

```js
function* generate() {
  yield 33;
  yield 99;
  throw Error("Tired of iterating!");
}

try {
  for (const value of generate()) {
    console.log(value);
  }
} catch (error) {
  console.error(error.message);
}

/* Output:
33
99
Tired of iterating!
*/
```

ここでは、`try` ブロックの中でハッピーパスを実行し、例外があれば `catch` でキャッチします。

## 非同期エラー処理

JavaScript はシングルスレッドで実行されるプログラム言語であり、原理的には同期的です。

ブラウザエンジンのようなホスト環境が JavaScript の機能を拡張させたことで、外部のシステムと通信したり、I/O 処理を行うための、たくさんの Web API が使えるようになりました。

ブラウザにおける非同期性の例は **タイムアウト(timeouts)、イベント(events)、プロミス(Promise)** があります。

**非同期の世界におけるエラー処理** は同期の世界におけるそれとは異なります。

いくつか例を見ていきましょう。

### タイマーのエラー処理

JavaScript を学び始めたばかりのとき、`try/catch/finally` 構文について学ぶと、あらゆるコードブロックを `try/catch/finally` 構文 で囲みたくなるかもしれません。

例えば以下のような関数を考えてみましょう:

```js
function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000)
}
```

この関数は、約 1 秒後にエラーを投げます。この例外を正しく扱うにはどうしたらよいでしょうか？

以下のようなコードは **上手く動きません** :

```js
function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000);
}

try {
  failAfterOneSecond();
} catch (error) {
  console.error(error.message);
}
```

前述したように、`try/catch` 構文は同期的です。一方で、ここでは `setTiemout` という、タイマー機能を持つブラウザの API を利用しています。

`setTimeout` に渡したコールバック関数が実行されるときには、既に`try/catch` 構文の実行は **終わっている** のです。上のプログラムは例外をキャッチすることができず、クラッシュしてしまいます。

2 つの異なったトラックが実行されているのです:

```text
Track A: --> try/catch
Track B: --> setTimeout --> callback --> throw
```

プログラムをクラッシュさせたくなければ、`try/catch` 構文を、`setTimeout` に渡しているコールバック関数の中に移動する必要があります。

しかし、このアプローチは多くの場合意味を成しません。後で見るように、 **Promises を用いた非同期エラー処理がより優れている** のです。

### イベントのエラー処理

Document Object Model (DOM) の HTML ノードは、`EventTarget` と連携しています。`EventTarget` は、ブラウザにおけるあらゆるイベントエミッターの共通の祖先といえる存在です。

これはつまり、[ページ上の全ての HTML 要素におけるイベントを取得することができる](https://www.valentinog.com/blog/event/#how-does-event-driven-applies-to-javascript-in-the-browser)ことを意味します。

（Node.js も今後のリリースで `EventTarget` をサポートする予定です）

**DOM イベントに対するエラー処理の仕組みは、非同期 Web API における仕組みと同様** です。

以下の例を考えてみましょう:

```js
const button = document.querySelector("button");

button.addEventListener("click", function() {
  throw Error("Can't touch this button!");
});
```

ここでは、ボタンがクリックされた瞬間に例外を投げています。どのようにその例外をキャッチするのでしょうか？以下のパターンは **上手く動作せず** 、プログラムはクラッシュしてしまいます:

```js
const button = document.querySelector("button");

try {
  button.addEventListener("click", function() {
    throw Error("Can't touch this button!");
  });
} catch (error) {
  console.error(error.message);
}
```

`setTimeout` の例で見たように、`addEventListener` に渡されるあらゆるコールバック関数は、非同期的に実行されます:

```text
Track A: --> try/catch
Track B: --> addEventListener --> callback --> throw
```

プログラムをクラッシュさせたくなければ、`addEventListener` のコールバック関数内部に `try/catch` 構文を移動する必要があります。

しかしここでも、そのようにする意味がほぼありません。

`setTimeout` の例で見たように、非同期処理コードの実行パスにおいて投げられた例外は **外側でキャッチすることができるものではなく** 、結果としてプログラムはクラッシュします。

次のセクションで、Promises と `async/await` がどのように非同期処理におけるエラー処理を手軽なものにするか見ていきます。

### onerror はどうだろう？

HTML 要素には、`onlick`、`onmouseenter`、`onchange` など多くのイベントハンドラがあります。

そのなかには、`onerror` もありますが、`throw` やその類のものとは何も関係がありません。

`onerror` イベントハンドラは、`<img>` や `<script>` のような HTML 要素が存在しないリソースを扱ったときにトリガーされます。

以下のような例を考えてみましょう:

```html
// omitted
<body>
<img src="nowhere-to-be-found.png" alt="So empty!">
</body>
// omitted
```

上記のような、存在しないリソースを参照する要素を含んだ HTML ドキュメントをブラウザで見ると、コンソールに以下のようなエラーが表示されます:

```text
GET http://localhost:5000/nowhere-to-be-found.png
[HTTP/1.1 404 Not Found 3ms]
```

JavaScript では、このエラーを以下のように「キャッチ」できるかもしれません:

```js
const image = document.querySelector("img");

image.onerror = function(event) {
  console.log(event);
};
```

より優れた形で書くと、以下のようになります:

```js
const image = document.querySelector("img");

image.addEventListener("error", function(event) {
  console.log(event);
});
```

このパターンは、**画像やスクリプトなどのリソースに欠損があった際に、代替となるリソースをローディングしたい場合** に便利です。

だたし、`onerror` は `throw` や `try/catch` とは何の関係もないことを覚えておいて下さい。

### Promise を用いたエラー処理

Promise によるエラー処理を説明するために、何度も登場している以下の例を「約束化(promisify)」させてみましょう。以下のコード例を編集していきます:

```js
function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);
```

単純に文字列もしくは例外を返す代わりに、成功とエラーを処理するための `Promise.reject` と `Promise.resolve` を利用してみましょう:

```js
function toUppercase(string) {
  if (typeof string !== "string") {
    return Promise.reject(TypeError("Wrong type given, expected a string"));
  }

  const result = string.toUpperCase();

  return Promise.resolve(result);
}
```

（厳密には、上記コードに非同期処理を行う部分はありませんが、説明するには十分です）

いま、`toUppercase` 関数は「約束」され、処理結果を扱うために `then` を、 **リジェクトされた Promise を処理するため** に `catch` を使うことができます:

```js
toUppercase(99)
  .then(result => result)
  .catch(error => console.error(error.message));
```

上記のコードは、以下のようなログを吐き出します:

```text
Wrong type given, expected a string
```

Promise において、`catch` はエラーを処理するための構成要素です。

`catch` や `then` に加え、`finally` もあります。この `finally` は、`try/catch` 構文における `finally` と似たものです。

Promise における `finally` も、返された Promise の結果に **関わらず** 実行されます:

```js
toUppercase(99)
  .then(result => result)
  .catch(error => console.error(error.message))
  .finally(() => console.log("Run baby, run"));
```

`then/catch/finally` に渡されたコールバック関数は、Microtask キューによって非同期に処理されることを覚えておいて下さい。これらは、イベントやタイマーよりも優先される **micro task** です。

### プロミス（Promise）、エラー（error）そしてスロー（throw）

Promise をリジェクトする際は、引数としてエラーオブジェクト渡すのが **ベストプラクティス** です:

```js
Promise.reject(TypeError("Wrong type given, expected a string"));
```

そうすることで、エラー処理の一貫性を保つことができます。他のチームメンバーが常に `error.message` にアクセスすることができますし、さらに重要なことに、スタックトレースを調査することができます。

`Promise.reject` に加えて、例外を投げることで Promise チェーンから抜け出すことができます。

以下のコード例を考えてみます:

```js
Promise.resolve("A string").then(value => {
  if (typeof value === "string") {
    throw TypeError("Expected a number!");
  }
});
```

文字列を返すとともに Promise をリゾルブし、そしてその直後に `throw` によって例外を投げています。

例外の伝播を食い止めるために、通常通り `catch` を使うことができます:

```js
Promise.resolve("A string")
  .then(value => {
    if (typeof value === "string") {
      throw TypeError("Expected a number!");
    }
  })
  .catch(reason => console.log(reason.message));
```

このパターンは、`fetch` を使う際によく用いられます。レスポンスオブジェクトのエラーチェックを行う例は以下の通りです:

```js
fetch("https://example-dev/api/")
  .then(response => {
    if (!response.ok) {
      throw Error(response.statusText);
    }

    return response.json();
  })
  .then(json => console.log(json));
```

ここでも、`catch` によって例外を受け取ることができます。もし例外を受け取ることに失敗した場合、あるいはあえて受け取らないことにした場合、 **例外はキャッチされるまでスタックに残り続けます。**

これは一概に悪いこととは言えませんが、環境によって、キャッチされていないリジェクトに対する挙動は異なります。

例えば、Node.js は将来的に、処理されていない Promise のリジェクトがあった場合は、プログラムをクラッシュさせる予定です:

```text
DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
```

必ずリジェクトはキャッチしましょう！

### "プロミス化"されたタイマーのエラー処理

タイマーとイベントにおいて、コールバック関数内で投げられた例外をキャッチすることは不可能ではありません。前のセクションで、以下のような例を挙げました:

```js
function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000);
}

// DOES NOT WORK
try {
  failAfterOneSecond();
} catch (error) {
  console.error(error.message);
}
```

Promise によって与えられた解決策は、コードの「プロミス化」です。基本的に、Promise でタイマーを囲みます:

```js
function failAfterOneSecond() {
  return new Promise((_, reject) => {
    setTimeout(() => {
      reject(Error("Something went wrong!"));
    }, 1000);
  });
}
```

`reject` によって Promise のリジェクトをセットし、エラーオブジェクトを渡します。

この時点で、`catch` をつかって例外を処理することができます:

```js
failAfterOneSecond().catch(reason => console.error(reason.message));
```

Tips: 成功した Promise の返り値の変数名として `value`、Promise のリジェクトの変数名として `reason` を使うことが一般的です。

Node.js は [promisify](https://nodejs.org/api/util.html#util_util_promisify_original) と呼ばれる、古い形で書かれたコールバック API をプロミス化するユーティリティを提供しています。

### Promise.all のエラー処理

Promise の static メソッドである `Promise.all` は Promise の配列を引数にとり、リゾルブした Promise の配列を返します。

```js
const promise1 = Promise.resolve("All good!");
const promise2 = Promise.resolve("All good here too!");

Promise.all([promise1, promise2]).then((results) => console.log(results));

// [ 'All good!', 'All good here too!' ]
```

渡した配列のどれか1つでもリジェクトされた場合、`Promise.all` は最初にリジェクトされた Promise のエラーとともにリジェクトします。

このような状況を扱うために、前のセクションで見たように `catch` が利用できます:

```js
const promise1 = Promise.resolve("All good!");
const promise2 = Promise.reject(Error("No good, sorry!"));
const promise3 = Promise.reject(Error("Bad day ..."));

Promise.all([promise1, promise2, promise3])
  .then(results => console.log(results))
  .catch(error => console.error(error.message));
```

`Promise.all` の実行結果に関わらず関数を実行するには、`finally` を利用します:

```js
Promise.all([promise1, promise2, promise3])
  .then(results => console.log(results))
  .catch(error => console.error(error.message))
  .finally(() => console.log("Always runs!"));
```

### Promise.any のエラー処理

`Promise.any` (Firefox > 79, Chrome > 85) は、`Promise.all` の反対の処理をする関数と考えることができます。

`Promise.all` が、渡した配列の中に 1 つでもリジェクトされるものがあった場合にエラーを返すのに対し、`Promise.any` はリジェクトが発生しても、リゾルブしたものが 1 つでもあればそれを返します。

`Promise.any` に渡した配列に含まれる **すべての Promise** がリジェクトされた場合、結果として得られるエラーは `AggregatedError` です。以下のようなコード例を考えてみましょう:

```js
const promise1 = Promise.reject(Error("No good, sorry!"));
const promise2 = Promise.reject(Error("Bad day ..."));

Promise.any([promise1, promise2])
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => console.log("Always runs!"));
```

`catch` を使ってエラーを処理しています。このコードの実行結果は以下の通りです:

```text
AggregateError: No Promise in Promise.any was resolved
Always runs!
```

`AggregatedError` オブジェクトは、通常の `Error` オブジェクトと同様のプロパティに加えて、`errors` プロパティを持っています:

```js
//
  .catch(error => console.error(error.errors))
//
```

このプロパティは、それぞれのリジェクトで返されたエラーの配列を格納しています:

```text
[Error: "No good, sorry!, Error: "Bad day ..."]
```

### Promise.race のエラー処理

`Promise.race` は、Promsie の配列を引数に取ります:

```js
const promise1 = Promise.resolve("The first!");
const promise2 = Promise.resolve("The second!");

Promise.race([promise1, promise2]).then(result => console.log(result));

// The first!
```

得られる返り値は、 **「レース」を制した 1 番着の Promise** です。

リジェクトされた場合はどうなるのでしょうか？リジェクトされる Promise が一番でなければ、`Promise.race` はリゾルブします:

```js
const promise1 = Promise.resolve("The first!");
const rejection = Promise.reject(Error("Ouch!"));
const promise2 = Promise.resolve("The second!");

Promise.race([promise1, rejection, promise2]).then(result =>
  console.log(result)
);

// The first!
```

もし **リジェクトが一番になった場合、`Promise.race` はリジェクトされ** 、以下のようにしてリジェクトをキャッチすることができます:

```js
const promise1 = Promise.resolve("The first!");
const rejection = Promise.reject(Error("Ouch!"));
const promise2 = Promise.resolve("The second!");

Promise.race([rejection, promise1, promise2])
  .then(result => console.log(result))
  .catch(error => console.error(error.message));

// Ouch!
```

### Promise.allSettled のエラー処理

`Promise.allSettled` は ECMAScript 2020 で追加される関数です。

この関数を使って処理するケースはそれほど多くありません。なぜなら、 **Promise のリジェクトがあったとしても、返り値は常にリゾルブされた Promise になるため** です。

以下のようなコード例を考えてみます:

```js
const promise1 = Promise.resolve("Good!");
const promise2 = Promise.reject(Error("No good, sorry!"));

Promise.allSettled([promise1, promise2])
  .then(results => console.log(results))
  .catch(error => console.error(error))
  .finally(() => console.log("Always runs!"));
```

上の例では、リゾルブする Promise とリジェクトされる Promise を 1 つずつ含め、配列として渡しています。

このケースでは、`catch` が実行されることはありません。代わりに `finally` が実行されます。

`then` 内の処理によってロギングされる結果は次の通りです:

```js
[
  { status: 'fulfilled', value: 'Good!' },
  {
    status: 'rejected',
    reason: Error: No good, sorry!
  }
]
```

### async/await のエラー処理

JavaScript の `async/await` は非同期関数を表しますが、コードを読む立場からみれば、同期関数の **可読性の高さ** の恩恵を受けているといえます。

話を単純にするために、何度も登場している同期関数 `toUppercase` を、`function` キーワードの前に `async` を付け足すことで、非同期関数に変換します:

```js
async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}
```

`async` というプレフィックスを使うことで、関数に **Promise を返す** ように仕向けることができるようになります。これはつまり、`then` や `catch`、`finally` といったチェーンが使えることを意味します:

```js
async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase("abc")
  .then(result => console.log(result))
  .catch(error => console.error(error.message))
  .finally(() => console.log("Always runs!"));
```

**`async` 関数内で例外を投げた** 場合、この例外は、 **裏側で機能している Promise をリジェクト** させます。

どんなエラーも、`catch` によってキャッチすることができます。

最も重要なことは、このスタイルに加えて同期関数と同様に **`try/catch/finally` 構文を使える**、ということです。

以下の例では、`toUppercase` 関数を `consumer` という他の関数から呼び出しています。`consumer` 内部では、`toUppercase` 関数を `try/catch/finally` 関数で囲っています:

```js
async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

async function consumer() {
  try {
    await toUppercase(98);
  } catch (error) {
    console.error(error.message);
  } finally {
    console.log("Always runs!");
  }
}

consumer(); // Returning Promise ignored
```

実行結果は以下の通りです:

```text
Wrong type given, expected a string
Always runs!
```

同様のトピックは次の記事でも扱っています: [How to Throw Errors From Async Functions in JavaScript?](https://www.valentinog.com/blog/throw-async/)

### 非同期ジェネレーターのエラー処理

JavaScript の **非同期ジェネレーター** は、通常の値の代わりに **Promise を yeild することができるジェネレーター関数** です。

`async` とジェネレーター関数を組み合わせて使います。イテレータオブジェクトが呼び出し元に対して Promise を返すジェネレーター関数です。

非同期ジェネレーターを作るために、`async` でプレフィックスした、`*` を持つ関数を定義します:

```js
async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}
```

Promise の仕組みに基づいているため、エラー処理に対しても同様のルールが適用されます。非同期ジェネレーター関数内の `throw` は Promise のリジェクトに繋がり、`catch` でキャッチすることができます。

**非同期ジェネレーター関数から Promise を取り出す** には、以下の 2 つのアプローチがあります。

- `then`ハンドラ
- 非同期イテレーション

上のコード例では、最初の 2 つの値が `yield` されたあとに、例外が投げられます。これは以下のようにできることを意味します:

```js
const go = asyncGenerator();

go.next().then(value => console.log(value));
go.next().then(value => console.log(value));
go.next().catch(reason => console.error(reason.message));
```

上記コードの実行結果は以下の通りです:

```text
{ value: 33, done: false }
{ value: 99, done: false }
Something went wrong!
```

もう1つのアプローチは、 `for await...of` で **非同期イテレーション** を用いる方法です。非同期イテレーションを用いるためには、呼び出し側の関数を `async` で囲む必要があります。

以下が完全なコード例です:

```js
async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}

async function consumer() {
  for await (const value of asyncGenerator()) {
    console.log(value);
  }
}

consumer();
```

`async/await` で見たように、潜在的に存在する例外は `try/catch` で処理することができます:

```js
async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}

async function consumer() {
  try {
    for await (const value of asyncGenerator()) {
      console.log(value);
    }
  } catch (error) {
    console.error(error.message);
  }
}

consumer();
```

実行結果は以下の通りです:

```js
33
99
Something went wrong!
```

非同期ジェネレーター関数の返り値であるイテレータオブジェクトには、同期ジェネレーター関数と同様に `throw()` メソッドがあります。

イテレータオブジェクトにおいて `throw()` メソッドを呼び出すと、例外は投げず、代わりにリジェクトされた Promise を投げます:

```js
async function* asyncGenerator() {
  yield 33;
  yield 99;
  yield 11;
}

const go = asyncGenerator();

go.next().then(value => console.log(value));
go.next().then(value => console.log(value));

go.throw(Error("Let's reject!"));

go.next().then(value => console.log(value)); // value is undefined
```

この状況を処理するには、以下のようにできます:

```js
go.throw(Error("Let's reject!")).catch(reason => console.error(reason.message));
```

ただし、イテレータオブジェクトの `throw()` は **ジェネレーター関数の内部** に例外を送ることを忘れないでおきましょう。これは、以下のようなパターンを適用することを意味します:

```js
async function* asyncGenerator() {
  try {
    yield 33;
    yield 99;
    yield 11;
  } catch (error) {
    console.error(error.message);
  }
}

const go = asyncGenerator();

go.next().then(value => console.log(value));
go.next().then(value => console.log(value));

go.throw(Error("Let's reject!"));

go.next().then(value => console.log(value)); // value is undefined
```

## Node.js のエラー処理

### Node.js の同期エラー処理

Node.js における同期エラー処理は、今までみてきた内容とほとんど同じです。

**同期コード** には、`try/catch/finally` が使えます。

しかしながら、非同期の世界に目を向けてみると、面白いことが起こります。

### Node.js の非同期エラー処理: コールバックパターン

非同期コードにおいては、Node.js は 2 つの書き方に依存しています:

- コールバックパターン
- イベントエミッター

**コールバックパターン** において **非同期 Node.js API** は、 **イベントループ** を通して処理され **コールスタック** が空になるとすぐに実行されるという関数を引数に取ります。

以下のようなコードを考えてみましょう:

```js
const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) console.error(error);
    // do stuff with the data
  });
}
```

上のコードからコールバック関数を抽出すると、どのようにエラーを処理することになっているかを見ることができます:

```js
//
function(error, data) {
    if (error) console.error(error);
    // do stuff with the data
  }
//
```

`fs.readFile` の実行過程においてエラーが発生した場合には、エラーオブジェクトを得ます。

この時点で、以下のことが可能です:

- 今までしてきたように、単純にエラーオブジェクトのログを表示する
- 例外を投げる
- 他のコールバックにエラーを渡す

例外を投げる場合は、以下のようにできます:

```js
const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) throw Error(error.message);
    // do stuff with the data
  });
}
```

しかし、DOM におけるイベントやタイマーと同様に、この例外は **プログラムをクラッシュ** させます。以下のように `try/catch` を用いてクラッシュを阻止しようとしても、うまくいきません:

```js
const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) throw Error(error.message);
    // do stuff with the data
  });
}

try {
  readDataset("not-here.txt");
} catch (error) {
  console.error(error.message);
}
```

プログラムをクラッシュさせたくなければ、他のコールバックにエラーを渡すことが望ましい方法です。

```js
const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) return errorHandler(error);
    // do stuff with the data
  });
}
```

ここで用いている `eventHandler` はその名前からも分かるように、エラーを処理するシンプルな関数です:

```js
function errorHandler(error) {
  console.error(error.message);
  // do something with the error:
  // - write to a log.
  // - send to an external logger.
}
```

### Node.js における非同期エラー処理: イベントエミッター

Node.js で行う多くのことは、 **イベント** に基づいています。ほとんどの場合、 **エミッターオブジェクト** と、いくつかのメッセージを待ち受けているオブザーバーとやり取りを行います。

Node.js のイベント駆動なモジュール（例えば [net](net) など）はすべて `EventEmitter` というルートクラスを継承しています。

Node.js の `EventEmitter` は、2 つの基本的なメソッド持っています: `on` と `emit` です。

以下のような単純な HTTP サーバーを考えてみましょう:

```js
const net = require("net");

const server = net.createServer().listen(8081, "127.0.0.1");

server.on("listening", function () {
  console.log("Server listening!");
});

server.on("connection", function (socket) {
  console.log("Client connected!");
  socket.end("Hello client!");
});
```

ここで、以下の 2 つのイベントを待ち受けています: **listening** と **connection** です。

これらのイベントに加えて、イベントエミッターはエラーが発生した際に **エラー** イベントも発火します。

もしこの上記コードのポート番号を 80 にして実行した場合、以下のような例外を得るでしょう:

```js
const net = require("net");

const server = net.createServer().listen(80, "127.0.0.1");

server.on("listening", function () {
  console.log("Server listening!");
});

server.on("connection", function (socket) {
  console.log("Client connected!");
  socket.end("Hello client!");
});
```

実行結果:

```text
events.js:291
      throw er; // Unhandled 'error' event
      ^

Error: listen EACCES: permission denied 127.0.0.1:80
Emitted 'error' event on Server instance at: ...
```

この例外をキャッチするためには、 **エラー** を待ち受けるイベントハンドラを登録します:

```js
server.on("error", function(error) {
  console.error(error.message);
});
```

以下のような結果を得ます:

```text
listen EACCES: permission denied 127.0.0.1:80
```

さらに、プログラムはクラッシュしません。

このトピックについての詳細は、"[Error Handling in Node.js](https://www.joyent.com/node-js/production/design/errors)" を読むと良いでしょう。

## まとめ

このガイドでは、シンプルな同期コードから高度な非同期な仕組みまで、JavaScript のエラー処理全般を扱いました。

JavaScript のプログラムでは、例外の発生の仕方は多岐にわたります。

同期コードの例外は最も単純に対処することができますが、 **非同期コード** における例外処理は **複雑になる** 場合があります。

一方で、ブラウザの新しい JavaScript API はほとんどすべて `Promise` に向かっています。普及したこのパターンは、`then/catch/finally` または `async/await` の`try/catch` を使って例外を処理することをより簡単にします。

このガイドを読んだ後は、 **プログラムで起こり得るすべての状況を認識して、例外を正しくキャッチすることができる** ようになっているはずです。

最後までお読み頂きありがとうございました！