【JavaScript入門】Promiseオブジェクト

 ここでは、Promiseオブジェクトの基本概念と、その使い方、及びPromiseチェーンによる非同期処理の流れを解説します。Promiseは「約束」という意味を持ち、非同期処理の結果を後で返すことを約束して、先に処理を進めるための仕組みです。以下の内容では、Promiseの生成方法、resolve/rejectによる状態の管理、.then()や.catch()による後続処理の登録方法、そしてPromiseチェーンによる処理の流れについて、表や具体例を交えて説明しています。

1.Promiseオブジェクトの基本概念

 Promiseオブジェクトは、非同期処理の結果(成功または失敗)を管理するためのオブジェクトです。新しいPromiseを生成するときは、以下のように2つの引数 resolve と reject を受け取るコールバック関数を指定します。
Promise生成時の基本構文は次の通りです。

new Promise((resolve, reject) => {
  // 時間のかかる処理などをここで実行
  // 正常に終了したときは resolve() を実行
  // 異常な場合は reject() を実行
});

また、Promiseオブジェクトは処理完了後の結果を受け取るために .then() や .catch() というメソッドを用います。
下記の表は、Promiseに関する主要な概念とメソッドのまとめです。

要素説明
Promise非同期処理の結果(成功または失敗)を管理するためのオブジェクト。
resolve()非同期処理が正常終了したときに呼び出し、成功時の値をPromiseに設定する。
reject()非同期処理が異常終了したときに呼び出し、失敗時の理由や値をPromiseに設定する。
.then()resolve()が呼ばれた後の処理を登録する。返された値はコールバック関数の引数として受け取る。
.catch()reject()が呼ばれた後の処理を登録する。エラー情報を引数で受け取る。

 Promiseオブジェクトは、.then() を重ねることで、連続した非同期処理を順次実行することができ、戻り値がPromiseであれば、次の .then() で待機して処理を続ける仕組みとなっています。

2.サンプルプログラム「promiseDemo.html」

 以下のサンプルコードでは、画面にはシンプルな説明用のテキストと装飾を施したボックスは表示されませんが、JavaScriptの console.log() を用いて、Promiseオブジェクトを利用した非同期処理の流れを確認できます。
 ファイル名は「promiseDemo.html」とし、タイトルには絵文字を加えています。なお、CSSで簡易的な装飾を行っています。

ファイル名: promiseDemo.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>📌 Promiseオブジェクト入門</title>
  <style>
    body {
      font-family: 'Segoe UI', sans-serif;
      background: #f0f8ff;
      margin: 20px;
    }
    #header {
      text-align: center;
      padding: 10px;
      border-bottom: 2px solid #ccc;
      margin-bottom: 20px;
    }
    #content {
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
      background: #fff;
      border: 1px solid #ddd;
      border-radius: 8px;
    }
  </style>
</head>
<body>
  <div id="header">
    <h1>📌 Promiseオブジェクト入門</h1>
  </div>
  <div id="content">
    <p>このサンプルでは、Promiseオブジェクトを用いた非同期処理の基本的な使い方について解説します。<br>
    コンソールに出力される順番を確認してください。</p>
  </div>

  <script>
    // 非同期処理を約束する関数 wait() を定義
    function wait(message, delay = 100) {
      console.log(message); // 非同期処理開始時にメッセージを表示
      return new Promise((resolve, reject) => {
        // delayミリ秒後に resolve() を実行する
        setTimeout(() => {
          resolve(message); // resolveの引数として message を返す
        }, delay);
      });
    }

    // メインの処理:Promiseチェーンによる非同期処理の例
    console.log('処理1'); // 同期処理:最初に実行される
    // wait() 関数を用いたPromiseチェーンの例
    wait('処理A', 100)
      .then(dataA => {
        // 処理A終了後に実行
        return wait('処理B', 100);
      })
      .then(dataB => {
        // 処理B終了後に実行
        return wait('処理C', 100);
      })
      .then(dataC => {
        // 処理C終了後に実行
        return wait('処理D', 100);
      })
      .then(dataD => {
        // 処理D終了後に実行
        return wait('処理E', 100);
      })
      .catch(error => {
        // エラーが発生した場合の処理
        console.error('エラーが発生しました:', error);
      });
    console.log('処理2'); // 同期処理:チェーン開始前に実行される
  </script>
</body>
</html>

ブラウザの出力例

3.プログラム解説

 このコードでは、PromiseとsetTimeoutを利用した非同期処理と、同期的な処理が組み合わさっているため、出力が「処理1」「処理A」「処理2」がまず表示され、その後にタイマーで遅れて「処理B」〜「処理E」が順に出力されます。特に「wait()」関数内の処理とPromiseチェーンがポイントになります。

以下、詳しく解説します。

3.1. コード全体の流れ

まず、コードの最初の部分で実行される同期処理と、非同期処理の起点が以下のようになっています。

console.log('処理1'); // (1) 同期処理

wait('処理A', 100)    // (2) wait() 呼び出し → 内部で '処理A' を出力(同期)
  .then(dataA => {    
    return wait('処理B', 100);  // (4) 時間経過後に実行され、'処理B' を出力(同期)
  })
  .then(dataB => {
    return wait('処理C', 100);  // (5) 時間経過後に実行され、'処理C' を出力(同期)
  })
  .then(dataC => {
    return wait('処理D', 100);  // (6) 時間経過後に実行され、'処理D' を出力(同期)
  })
  .then(dataD => {
    return wait('処理E', 100);  // (7) 時間経過後に実行され、'処理E' を出力(同期)
  })
  .catch(error => {
    console.error('エラーが発生しました:', error);
  });

console.log('処理2'); // (3) 同期処理

3.2. 各処理の詳細

同期処理部分

(1) console.log('処理1');
この行はプログラム開始直後に実行され、すぐに「処理1」がコンソールに出力されます。

(2) wait('処理A', 100) の呼び出し
この関数呼び出しが始まると、関数wait()の中で以下の処理が行われます。

即時ログ

console.log(message); 

が実行されるため、呼び出し直後に「処理A」が出力されます。

Promiseの返却と非同期処理
 setTimeoutで100ミリ秒後にresolve(message)が実行されるようにスケジュールされ、Promiseが返されます。なお、このタイマーによる解決は非同期処理になります。

(3) console.log('処理2');
 wait('処理A', 100)の内部でconsole.log('処理A')が実行された直後、メインスレッドは次の行に進み、この行により「処理2」が出力されます。つまり、setTimeoutの待機中であっても同期処理は即時に実行されます。

非同期処理(Promiseチェーン)の部分

(4) .then(dataA => { return wait('処理B', 100); })
 「処理A」のPromiseが100ミリ秒後に解決されると、最初の.thenのコールバックが呼ばれます。
その中で再度wait('処理B', 100)が呼び出され、同様に関数内部で即時に「処理B」が出力され、その後にさらに100ミリ秒後に解決されるPromiseが返されます。

(5) 以降の.thenコールバック
 同様の流れで、Promiseチェーン内で順にwait('処理C', 100)wait('処理D', 100)wait('処理E', 100)が呼び出され、各呼び出しで即時にそのメッセージがコンソールに出力され、各PromiseはsetTimeoutで100ミリ秒後に解決されます。

なぜこの順番になるのか?

同期処理と非同期処理の違いが鍵です。

  • 同期処理
    プログラムは上から順に実行されるため、console.log('処理1')console.log('処理2') は、待機(setTimeoutでの遅延)とは無関係に即時に実行されます。
  • 非同期処理(setTimeoutとPromiseチェーン)
    wait()関数内の console.log(message) は、関数が呼ばれた時点で即時に実行されますが、resolve() を実行するための処理は100ミリ秒の遅延があります。
    そのため、以下の流れになっています。
    1. プログラムの開始
      「処理1」→「処理A」→「処理2」がすぐに出力されます。
    2. 100ミリ秒後
      ・最初のwait()のsetTimeoutが終了し、Promiseが解決され、.thenコールバックでwait('処理B', 100)が呼ばれる。
      ・この呼び出しによって「処理B」が即時に出力される。
    3. さらに100ミリ秒後
      Promiseチェーンが次の.thenへと進み、「処理C」が出力される。
    4. 以下同様に、順次「処理D」「処理E」が出力されます。

3.3.出力の順番のまとめ

  1. 最初に同期実行されるもの
    ・「処理1」
    wait('処理A', 100) が呼ばれ、内部で即時「処理A」が出力
    ・「処理2」
  2. その後、非同期で順番に実行されるもの
    ・最初のPromiseが解決した後に wait('処理B', 100) が呼ばれ、即時「処理B」が出力
    ・次のPromiseの解決後に wait('処理C', 100) → 「処理C」出力
    ・次に wait('処理D', 100) → 「処理D」出力
    ・最後に wait('処理E', 100) → 「処理E」出力

このため、最終的なコンソール出力は、

処理1
処理A
処理2
処理B
処理C
処理D
処理E

となります。

まとめ

ここでは、Promiseオブジェクトの基本的な使い方と、その特徴について解説しました。

  • Promiseは、非同期処理の結果を管理するためのオブジェクトであり、成功時には resolve()、失敗時には reject() を実行して状態を変更します。
  • .then() や .catch() を用いて、非同期処理が完了した際の後続の処理を登録できます。
  • Promiseチェーンを用いることで、複数の非同期処理を順次、かつ分かりやすく記述することが可能です。

 今回のサンプル「promiseDemo.html」では、setTimeout() を用いた wait() 関数を利用し、Promiseチェーンによる非同期処理の流れと、その出力順を確認できました。今後、さらに async/await の利用や、実際の通信処理との組み合わせ例などを通じて、Promiseの活用方法について深く理解を進めていきましょう。