Promiseのthenとcatchの使い分け方!成功時とエラー時の処理を書くポイント

[PR]

JavaScript・フロントエンド

非同期処理をJavaScriptで書く際、Promiseという仕組みは必須のツールです。特に“成功時の処理”と“エラー時の処理”をどう明確にするか。この点に疑問を持つ方は多いでしょう。thenだけで済ませるのか、catchを使うべきか。この記事ではPromise then catch 使い分けをテーマに、それぞれの特徴・使いどころ・回避すべき落とし穴までを丁寧に解説します。非同期処理がより安全で読みやすくなるポイントを抑えていただけます。

Promise then catch 使い分け が重要な理由と基本の違い

Promise then catch 使い分けを理解する第一歩は、thenとcatchがそれぞれどういう仕組みで動いているかを知ることです。thenはPromiseが成功した時の処理を記述し、失敗した時には第二引数のonRejectedを指定できます。一方、catchはPromiseが拒否された場合(rejectされたり、thenの中で例外が起きたりした場合)に処理を行います。thenの第二引数にエラー処理を入れる方法もありますが、catchを使う方が可読性とメンテナンス性に優れます。Promiseチェーン全体のエラーをまとめて扱いたいときにはcatchが便利です。

thenの基本的な振る舞い

thenには第一引数に成功時コールバック、第二引数に失敗時コールバックを取る形式があります。しかしこの書き方は注意点があります。then内で例外を投げた場合、第二引数だけではその例外を捕まえられないことがあります。Promiseが拒否された場合にはonRejectedが呼ばれますが、thenの第一引数で例外が起きると、その例外は次のcatchでなければ拾えないことがあるからです。ですので、thenには成功処理のみを書き、エラー処理は専らcatchでまとめるスタイルが推奨されます。

catchの役割と利点

catchはPromiseチェーンで発生したrejectまたはthenの例外をまとめて捕捉できます。たとえば途中で何かしらの非同期処理が失敗しても、catchを最後にひとつ設けておけばそのエラーを一箇所で処理できます。コードの重複を減らせますし、どの位置でエラーが発生したかを追いやすくなります。さらにcatchはthen(null, onRejected)のショートハンドであり、可読性が高い書き方です。

then(success, fail) と then(success).catch(fail) の違い

then(success, fail) は成功と失敗の両方のコールバックを同じthenにまとめます。一方、then(success).catch(fail) は成功のみを先に処理し、失敗はcatchでまとめて扱います。これらは似て非なるもので、then(success, fail) によって処理された失敗はチェーンが続行される場合があるなどの微妙な違いがあります。catchを使ったスタイルの方が、非同期処理の途中で起きた例外やrejectを一律に扱いやすいためエラー処理が漏れにくくなります。

thenとcatchを実際にどう使い分けるかのベストプラクティス

実際の開発ではPromise then catch 使い分けのコツを押さえておくことでコードの品質が大きく変わります。このセクションでは具体的な利用シーンと、それぞれどの方法が適しているかを示します。成功時処理・エラー時処理・チェーン構造など、リアルな例を交えて説明しますので、実務にも応用しやすい内容です。

成功時の処理だけを行いたい場合

非同期操作が完了したときだけ処理をしたい、エラー時は別でまとめて扱いたいという場合には、thenで成功処理を書き、catchでまとめてエラー処理を行うパターンが最もシンプルです。then(successCallback)だけを使い、失敗時にはcatch(errorCallback)で対応することで、可読性と保守性を確保できます。成功処理とエラー処理が別々に明示されるので処理の意図が追いやすくなります。

途中でエラー発生時にチェーンを中断させたい場合

処理が何段階にも分かれており、どこかで失敗したらそれ以降を実行したくないケースがあります。この場合、各thenの中で例外を投げたりrejectを返し、その後のthenが実行されないようcatchを使ってチェーンの最後で処理を止めます。then(success).catch(fail)の形式がここで威力を発揮します。then(success, fail) だとfailで処理した後に次のthenが実行されるケースがあり得るため、中断したいならcatchが確実です。

部分的にエラー処理を分けたい場合

例えば非同期処理のうち「任意の処理」でエラーが起きても全体には影響させたくないけれど、「重要な処理」では必ずキャッチしたいという場合があります。このようなときは、任意処理部分にはcatchをネストしたり、その部分だけthen(…).catch(…)でエラーを抑制し、それ以降の重要処理のthenチェーンにはcatchを最後に設けるというスタイルが有効です。こうすることで部分的なエラーを無視しつつ、重要事項の失敗は必ず報告できます。

thenとcatchの注意点と避けるべきパターン

Promise then catch 使い分けにおいて、誤解やバグの温床となるパターンがあります。これらを知っておくことが信頼性のある非同期コードを書く鍵です。ここでは典型的な落とし穴と、それを避けるための具体的な指針を解説します。

thenにおけるonRejectedを使いすぎて可読性が落ちる場合

then(success, error) を頻繁に使うと、成功処理と失敗処理が混在して見にくくなり、どの部分がエラー発生源か追いにくくなります。さらに複数のthenが連なるときにerrorの取り扱いが各thenに散らばるため、メンテナンスが難しくなります。こうした状況を避けるため、成功処理はthen(success) のみに集中させ、catchで失敗処理を一箇所にまとめる設計を心がけるべきです。

エラーが漏れる未処理拒否(Unhandled Promise Rejection)

thenだけでエラー処理を記述しなかったりチェーンの最後にcatchを置かなかったりすると、rejectや例外がキャッチされず未処理の拒否が発生します。これによりブラウザや実行環境に警告が出たり、将来的にプロセス停止の原因になったりします。Promiseチェーンの最後には必ずcatchを設けることをルール化するとリスクを抑えられます。

ネストしすぎて処理が複雑になる構造

任意にthenをネストしてしまうと、構造が深くなり、どのcatchがどのthenに対応しているかが分かりにくくなります。ネストは部分的なエラー処理のために使うべきで、それ以外はチェーンを平坦に保つことが望ましいです。可読性・デバッグ性の観点から、処理の階層を浅くし、重要な部分は明確に整理することが肝心です。

then と catch を比較した例で理解する具体シナリオ

概念だけでは理解が難しい部分もありますので、ここでは具体例を用いてPromise then catch の使い分けがどう動くかを比較します。処理の流れ・結果・エラー箇所などを表で整理することで、どの方法がそのシナリオでは適切かが見えてきます。

以下は非同期処理を三段階で行うシナリオです。各段階で成功・失敗の可能性があり、それをthenとcatchで扱った場合の挙動を比較します。

パターン 構成 メリット デメリット
then(success, error) 複数用いる 処理1.then(success1,error1).then(success2,error2).then(success3,error3) 各段階で個別にエラー処理できる。失敗時の処理を即座に記述可能。 コードが複雑になる。失敗処理が散らばり追いにくく、thenの第一引数での例外にcatchが効かない場合がある。
then(success).catch(fail) 形式 処理1.then(success1).then(success2).then(success3).catch(fail) 可読性が高い。失敗箇所を一箇所で捕捉できる。エラー漏れが少ない。 異なる失敗パターンに個別対応したい場合は分岐処理が追加で必要。

成功・失敗の具体的なフロー例

たとえば非同期処理A→B→Cがあり、Bでエラーが発生した際、then(success, error)ではBのerrorで分岐しその後のCを実行するかどうかを明示的に判断する必要があります。一方then(success).catch(error)ではBの成功が失敗に変わり、catchに飛び、Cは実行されません。中断の流れを自然に表現できるので、複数処理を順序通り実行したい場面では後者のスタイルが適します。

可視性とデバッグへの影響

コードレビューやデバッグの際、catchをチェーンの終端にひとつ置いておくスタイルのほうがどこでエラーが起きたか追いやすいです。エラー処理が点在しているとログ出力やスタックトレースの中で原因を探す負荷が増します。collective error handling はエラー監視ツールとの統合にも向いており、エラー収集・通知が効率化されます。

then と catch の利用がさらに進化した async/awaitとの組み合わせ方

JavaScriptの非同期処理手段としてasync/awaitが広く使われています。Promise then catch 使い分けで得た知見はasync/awaitでも応用可能です。ここではasync/awaitとの組み合わせでどのようにthen/catchを併用したり、代替したりできるかを具体的に説明します。

async関数内での try/catch を使った例

async 関数内では同期的なコードに近い書き方ができるため、try/catchで非同期処理の成功・失敗を扱うのが自然です。awaitを使うとPromiseを待って結果を取得でき、エラーが起きた場合はcatchまたはtry/catchで処理できます。then/catch のチェーンを無理に続けるより、async 関数内で局所的に try/catch を使う方がコードが明快になります。

then/catch と async/await の併用時の注意

async/await を使っている場合でも、Promise を返すAPIや既存の then/catch チェーンとの組み合わせが必要な場面があります。このとき、混在させる順序に注意が必要です。awaitの結果をthenで受け取ったあとcatchで処理するパターンや、async 関数の外側でthen/catchを使ってハンドリングするパターンなどがあります。どちらの場合も未処理例外が発生しないように catch を必ず設けることが重要です。

finally を使ってクリーンアップ処理を行う

非同期処理の終了時に必ず実行したい処理(ローディング終了・リソース解放など)は finally を使うと便利です。then/catch のどちらでも成功・失敗を問わず処理を行えるため、クリーンアップロジックを集中させて記述できます。チェーンの最後に finally を使い、catch で失敗処理を確実に行った後、finally で後片付けを行うという構造が読みやすさ・信頼性の両立に優れています。

実践的なコード例:Promise then catch 使い分け パターン集

ここまで学んだことを踏まえて、実際に使われる形のコードパターンをいくつか紹介します。状況に応じてどのパターンを採用すればよいかがわかるようになります。

パターン1:単純な非同期取得処理

APIからデータを取得し、それを画面表示したいだけのシンプルなケースでは、thenで成功処理、catchでエラー処理という形が最も自然です。以下のコード例では、データ取得成功時には表示、失敗時にはエラーメッセージを出すだけです。

fetchData()
  .then(data => {
    // 成功時処理:データを表示
  })
  .catch(error => {
    // エラー時処理:エラーメッセージ表示
  });

パターン2:複数ステップの処理で中断を許すケース

次に処理A→処理B→処理Cという流れで、どれかでエラーが起きたらそこで全体を中断したい場合の例です。then(success).catch(fail) 形式で書くと、どの段階で拒否されたか捕捉でき、中断処理が自然になります。

processA()
  .then(resultA => processB(resultA))
  .then(resultB => processC(resultB))
  .then(finalResult => {
    // 全て成功時処理
  })
  .catch(error => {
    // A・B・Cのいずれかでエラー処理
  });

パターン3:ネストされた任意処理を持つ例

任意処理(エラーを無視してもよい部分)と、重要処理(必ず成功させたい部分)が混在するケースです。ネストしたthen内にcatchを設けて任意処理のエラーを抑制し、その外側で重要処理の流れをコントロールする構造にします。

doCritical()
  .then(cr => {
    return optionalStep(cr)
      .then(opt => extraOpt(opt))
      .catch(optErr => {
        // 任意処理のエラーを抑制
      });
  })
  .then(crFinal => continueCritical(crFinal))
  .catch(err => {
    // 重要処理のエラー
  });

まとめ

Promise then catch 使い分けは、JavaScriptで非同期処理を堅牢かつ可読性の高いものにするための重要な技術です。then(success)+catch(fail)形式を基本形とし、then(success,fail)を限定的に使うことでエラー処理が散らばることを防げます。特に複数の処理を順に行うチェーンや、中断を許すケースではcatchを末尾に置くスタイルが有効です。

async/awaitとの併用やfinallyによるクリーンアップなども含め、処理の意図を明確にし一貫性を保つことが重要です。Promise then catch 使い分けを意識することで、エラーや失敗への対応が漏れるリスクを低減し、保守性や信頼性の高いコードを書くことができるようになります。

関連記事

特集記事

コメント

この記事へのトラックバックはありません。

TOP
CLOSE