【ITニュース解説】Promise, Callback Hell, Synchronous and Asynchronous in Javascript

2025年09月07日に「Dev.to」が公開したITニュース「Promise, Callback Hell, Synchronous and Asynchronous in Javascript」について初心者にもわかりやすいように丁寧に解説しています。

作成日: 更新日:

ITニュース概要

JavaScriptのPromiseは、非同期処理の結果(成功または失敗)を将来の確定値として扱うオブジェクトだ。コールバック地獄は、コールバック関数を重ねすぎてコードが読みにくくなる問題である。同期処理は命令を順序通り実行し、前の処理が終わるまで待つ。非同期処理はバックグラウンドで実行され、待たずに次の処理へ進む。

ITニュース解説

プログラムは、私たちがコンピュータに「何を」「どのように」実行してほしいかを伝える指示の集まりだ。これらの指示には、大きく分けて「同期処理」と「非同期処理」という二つの実行方法がある。この違いを理解することは、システムエンジニアを目指す上で非常に重要となる。

まず「同期処理」について説明する。同期処理とは、プログラムがコードを上から順番に、一行ずつ実行していく方法だ。ある処理が完了するまで、次の処理は一切開始されない。これはまるで、目の前のタスクが完全に終わるのをじっと待ってから、次のタスクに取り掛かるようなものだ。例えば、「スタート」と表示し、次に「タスク完了」と表示し、最後に「エンド」と表示するプログラムがあるとする。この場合、出力は必ず「スタート」→「タスク完了」→「エンド」の順になる。途中の「タスク完了」に時間がかかったとしても、その処理が終わるまで「エンド」は表示されない。同期処理は流れが分かりやすく、シンプルだが、もし途中に非常に時間のかかる処理があった場合、プログラム全体がその処理の完了を待つことになり、その間は他の何もできなくなるという欠点がある。

これに対して「非同期処理」は、時間のかかる処理をバックグラウンドで実行させ、その完了を待たずにプログラムが次の処理へと進む方法を指す。これは、例えば「長い時間かかる作業は他の人に任せておいて、自分は次の作業に取り掛かる」といったイメージに近い。非同期処理を利用すると、ウェブサイトでデータを取得するような時間のかかる操作を実行している間でも、ユーザーインターフェースが固まらず、他の操作を続けられるといった利点がある。先の例で、もし「タスク完了」に2秒かかる非同期処理を使ったとしよう。プログラムは「スタート」と表示した後、「タスク完了」の処理をバックグラウンドで開始し、その完了を待たずにすぐに次の「エンド」を表示する。そして2秒後にバックグラウンドで実行されていた「タスク完了」の結果が表示される。つまり出力は「スタート」→「エンド」→「タスク完了」の順になるのだ。非同期処理は、プログラムの応答性を高め、効率的な処理を実現するために不可欠な技術である。

しかし、非同期処理には特有の課題も伴う。いつ結果が返ってくるか分からない非同期処理の結果を、プログラムの次のステップで利用したい場合、その結果を受け取るための特別な仕組みが必要となる。その一つの方法が「コールバック関数」だ。コールバック関数とは、別の関数に引数として渡される関数のことで、特定の非同期タスクが完了した後に実行される。たとえば、データ取得が終わったら、そのデータを画面に表示するというような場合に使われる。

問題は、複数の非同期タスクを順番に実行したい場合に発生する。例えば、ソフトウェア開発プロジェクトを「要件分析」「計画」「設計」「開発」「テスト」「デプロイ」という複数のステップで進めるとしよう。それぞれのステップが非同期処理であり、前のステップが完了したら次のステップを実行する必要がある。これをコールバック関数で実現しようとすると、最初のコールバックの中に次のコールバックを書き、さらにその中に次のコールバックを書いていくという形になる。結果として、コードのインデントがどんどん深くなり、まるで複数のネストされたボックスの中に次のボックスが入っているかのように見える。このような状態を「Callback Hell(コールバック地獄)」と呼ぶ。Callback Hellに陥ったコードは、非常に読みにくく、どこで何が起きているのかを把握するのが困難になり、エラーが発生した際のデバッグや、後からの機能追加・修正が極めて難しくなる。

このCallback Hellの問題を解決し、非同期処理をより分かりやすく、管理しやすくするために導入されたのが「Promise(プロミス)」という概念である。Promiseは、JavaScriptにおける非同期操作の最終的な完了または失敗、およびその結果値を表現するオブジェクトだ。これは、将来のある時点で利用可能になる値(成功か失敗か)の「プレースホルダー」のようなものだと考えると良いだろう。例えば、Webサーバーへのデータ要求はすぐに結果が返ってくるわけではなく、しばらく時間がかかる。Promiseは、この「いずれ返ってくるであろう結果」を一旦保留し、その結果が出たときに何をするかをあらかじめ予約しておくことができる。

Promiseには、その非同期操作の進行状況を示す三つの状態がある。一つ目は「Pending(保留中)」で、非同期操作がまだ実行中で、結果がまだ出ていない状態だ。二つ目は「Fulfilled(完了)」または「Resolved(解決済み)」で、非同期操作が成功し、期待された値が得られた状態を指す。三つ目は「Rejected(拒否済み)」で、非同期操作が失敗し、エラーが発生した状態だ。非同期操作が成功した場合はresolveという関数を呼び出し結果値を渡し、失敗した場合はrejectという関数を呼び出しエラー情報を渡す。

Promiseの大きな利点は、その使い方にある。先のソフトウェア開発の例をPromiseで書き直すと、Callback Hellのように深くネストされた構造にはならない。Promiseは、.then()というメソッドを使って、成功した次の処理をチェーンのように繋いでいくことができる。最初のPromiseが成功したら、.then()で指定した次の処理が実行され、さらにそれが成功したら次の.then()が実行される、という具合に、まるで順番に作業をバトンタッチしていくかのように記述できるのだ。これにより、コードは直線的になり、上から下へと読み進めやすくなる。もし途中のいずれかのステップでエラーが発生した場合でも、.catch()メソッドをチェーンの最後に付けておくことで、そのエラーを一箇所でまとめて捕捉し、適切に処理することができる。また、.finally()メソッドを使えば、Promiseが成功しても失敗しても、最終的に必ず実行したい処理を記述することが可能だ。このようにPromiseを使うことで、非同期処理の流れを明確にし、エラーハンドリングも簡潔に行えるため、Callback Hellのような複雑さを避け、読みやすく保守しやすいコードを書くことができるようになる。

同期処理と非同期処理のどちらを使うかは、プログラムの要件によって適切に選択する必要がある。そして、特に非同期処理を扱う際には、Callback Hellという問題に直面することが多く、その解決策としてPromiseは非常に強力なツールとなる。システムエンジニアとして、これらの非同期処理の概念とPromiseのようなパターンを理解し、適切に使いこなせるようになることは、現代のソフトウェア開発において基礎中の基礎であり、不可欠なスキルと言えるだろう。

関連コンテンツ