JavaScript アドバンス

JavaScript Promises

1. なぜ Promise なのか?

JavaScript プロミス(Promises)は、非同期(Asynchronous)処理をより簡単に扱うために導入されました。
Promise オブジェクトは、非同期操作の「完了」または「失敗」を表します。

多くのコールバック(Callbacks)を使用すると、コードの可読性が低下し、メンテナンスが困難になります。

例(コールバック地獄)

step1(function(r1) {
  step2(r1, function(r2) {
    step3(r2, function(r3) {
      console.log(r3);
    });
  });
});

このような記述スタイルは、しばしば「コールバック地獄(Callback Hell)」と呼ばれます。
Promise を使用することで、同じロジックをよりクリーンに記述できます。

Promise は、将来のある時点で利用可能になる値のプレースホルダー(Placeholder)として機能し、従来のコールバックよりも洗練された方法で非同期コードを処理することを可能にします。

2. Promise の状態 (Promise States)

Promise は、以下の3つの排他的な状態のいずれかを取ります。

  • Pending(ペンディング): 初期状態。操作が開始されたが、まだ完了していない。
  • Fulfilled(フルフィルド): 操作が正常に完了し、値が利用可能な状態。
  • Rejected(リジェクテッド): 操作が失敗し、エラー理由が利用可能な状態。

Promise が Fulfilled または Rejected になった場合、その Promise は「Settled(セットルド / 確定)」したとみなされます。

3. Promise の作成 (Creating a Promise)

let myPromise = new Promise(function(resolve, reject) {

// 時間のかかる処理をここに記述

  resolve(value); // 成功時
  reject(value);  // エラー発生時
});

Promise コンストラクタは、2つの引数(Parameters)を持つ関数を受け取ります。

引数説明
resolve処理が正常に終了したときに実行する関数
rejectエラーで終了したときに実行する関数

4. Promise の使い方

Promise を利用(コンシューム)する方法は以下の通りです。

myPromise.then(
  function(value) { /* 成功時のコード */ },
  function(value) { /* 失敗時のコード */ }
);

.then() は2つの引数を取ります。1つは成功時、もう1つは失敗時のコールバック関数です。
これらは両方ともオプションであるため、成功時のみ、あるいは失敗時のみのコールバックを追加することも可能です。

実装例

// Promise オブジェクトの作成
let myPromise = new Promise(function(resolve, reject) {
  let ok = true;

  // 時間のかかる処理

  if (ok) {
    resolve("OK");
  } else {
    reject("Error");
  }
});

// then() を使用して結果を表示
myPromise.then(
  function(value) { myDisplayer(value); },
  function(value) { myDisplayer(value); }
);

Promise は将来利用可能になる値を表すコンテナであり、その結果は「値」または「エラー」のいずれかになります。

5. JavaScript Promise オブジェクト

Promise オブジェクトには、値を生成する「Producing Code(生成コード)」と、それを呼び出す「Consuming Code(消費コード)」の両方が含まれます。

5.1 Promise の構文構造

let myPromise = new Promise(function(resolve, reject) {
  // "Producing Code" (時間がかかる可能性がある処理)
  resolve(value); // 成功時
  reject(value);  // 失敗時
});

// "Consuming Code" (Promise が確定するのを待機)
myPromise.then(
  function(value) { /* 成功時の処理 */ },
  function(value) { /* 失敗時の処理 */ }
);

生成コードが結果を得たとき、以下のいずれかのコールバックを呼び出す必要があります。

状況呼び出す関数
成功 (Success)resolve(value)
失敗 (Error)reject(value)

Promise が resolve または reject されるのは、一度きりです。

5.2 Promise オブジェクトのプロパティ

Promise オブジェクトは、内部的に state(状態)と result(結果)という2つのプロパティをサポートしています。

myPromise.statemyPromise.result
"pending"undefined
"fulfilled"結果の値 (result value)
"rejected"エラーオブジェクト (error object)

stateresult プロパティに直接アクセスすることはできません。Promise を扱うには、必ず Promise メソッドを使用する必要があります。

6. コアメソッドと使用方法

Promise オブジェクトに付随するメソッドを使用して、結果を処理します。

  • .then(onFulfilled, onRejected): 成功時と失敗時両方のハンドラを登録します。新しい Promise を返すため、メソッドチェーン(Method Chaining)が可能です。
  • .catch(onRejected): .then(null, onRejected) のショートハンドであり、通常、チェーンの最後でエラーを捕捉するために使用されます。
  • .finally(onFinally): 成功・失敗に関わらず、Promise が確定したときに必ず呼び出されます。クリーンアップ処理に有用です。

6.1 then と catch の使用例

let promise = Promise.resolve("OK");

promise
.then(function(value) {
  console.log(value);
})
.catch(function(value) {
  myDisplayer(value);
});

7. Promise を返す

.then() 内から Promise を返すことで、強力なメソッドチェーンを作成できます。

// ステップ実行する3つの関数
function step1() {
  return Promise.resolve("A");
}
function step2(value) {
  return Promise.resolve(value + "B");
}
function step3(value) {
  return Promise.resolve(value + "C");
}

// ステップごとに実行
step1()
.then(function(value) {
  return step2(value);
})
.then(function(value) {
  return step3(value);
})
.then(function(value) {
  myDisplayer(value);
});

7.1 catch の配置場所

エラーハンドリングはチェーンの最後にまとめることができます。1つの .catch() で、それより上のどのステップで発生したエラーも捕捉可能です。

step1()
.then(function(value) {
  return step2(value);
})
.then(function(value) {
  return step3(value);
})
.catch(function(error) {
  console.log(error);
});

8. 初心者が陥りやすいミス

Promise を返すのを忘れると、チェーンが壊れてしまいます。

間違った例

step1()
.then(function(value) {
  step2(value); // return がない!
})
.then(function(value) {
  console.log(value); // 前のステップを待たずに実行されてしまう
});

最初の .then() から何も返されないため、2番目の .then() が早すぎるタイミングで実行されてしまいます。非同期ステップを開始する場合は、必ずそれを return してください。

9. 実践的な JavaScript における Promise

多くの Web API が Promise を返します。その代表例が fetch() です。

fetch("data.json")
.then(function(response) {
  return response.json();
})
.then(function(data) {
  console.log(data);
})
.catch(function(error) {
  console.log(error);
});

10. Promise API のスタティックメソッド

複数の Promise を一度に扱うためのスタティックメソッド(Static Methods)も用意されています。

  • Promise.all(iterable): すべての Promise が成功した時に解決します。1つでも失敗すると即座に拒否(Reject)されます。
  • Promise.allSettled(iterable): すべての Promise が確定(成功または失敗)するのを待ち、各結果の配列を返します。
  • Promise.race(iterable): 最も早く確定した Promise の結果を返します。
  • Promise.any(iterable): 最初に成功した Promise の値を返します。すべて失敗した場合のみ拒否されます。

11. タイムアウトの待機

コールバックによる例

setTimeout(function() { myFunction("大好きです!!!"); }, 3000);

function myFunction(value) {
  document.getElementById("demo").innerHTML = value;
}

Promise による例

let myPromise = new Promise(function(myResolve, myReject) {
  setTimeout(function() { myResolve("大好きです!!"); }, 3000);
});

myPromise.then(function(value) {
  document.getElementById("demo").innerHTML = value;
});

12. ファイルの待機

コールバックによる例 (XMLHttpRequest)

function getFile(myCallback) {
  let req = new XMLHttpRequest();
  req.open('GET', "mycar.html");
  req.onload = function() {
    if (req.status == 200) {
      myCallback(req.responseText);
    } else {
      myCallback("Error: " + req.status);
    }
  }
  req.send();
}

getFile(myDisplayer);

Promise による例

let myPromise = new Promise(function(myResolve, myReject) {
  let req = new XMLHttpRequest();
  req.open('GET', "mycar.html");
  req.onload = function() {
    if (req.status == 200) {
      myResolve(req.response);
    } else {
      myReject("ファイルが見つかりません");
    }
  };
  req.send();
});

myPromise.then(
  function(value) { myDisplayer(value); },
  function(error) { myDisplayer(error); }
);