コールバック関数を使った非同期処理は、コールバック地獄への懸念があります。
しかし、ES6からはPromiseという新しい非同期処理の仕組みが導入され、コールバック地獄の問題が解消されました。

Promiseはコールバック関数に比べて処理のフローを細かく制御することができ、非同期処理には幅広く使われています。
そこで今回は、Promiseを使った非同期処理の方法を解説していきます。

Promise

Promiseとは、非同期処理の結果を保持するオブジェクトのことです。
また、そのPromiseオブジェクトを利用した処理の仕組みのことを指します。

Promiseは、以下の3つのステータスを持っています。

  • Pending:初期の状態、または処理待ちの状態
  • Fullfilled:処理が成功して完了した状態
  • Rejected:処理が失敗した状態

これらのPromiseオブジェクトのステータスによって、処理を分岐していくことがPromiseの特徴です。

Promiseオブジェクトの生成

以下は、Promiseオブジェクトを生成するための構文です。
Promiseの引数には2つの関数を渡し第一引数にはresolve、第二引数にはrejectを渡します。

const promise = new Promise((resolve, reject) => {});

処理が成功ならresolve、失敗ならrejectのPromiseオブジェクトを返します。

const promise = new Promise((resolve, reject) => {
  // 何かしらの処理
  const result = something();
  // 処理に成功ならresolve、失敗ならreject
  if (result === 'success') {
    resolve(result);
  } else {
    reject('fail')
  }
});

thenメソッド

thenメソッドは、fullfilledステータス、またはrejectedステータスのPromiseオブジェクトを受け取ることができます。

resolveされた時にはその成功した結果を受け取り、rejectされた時にはそのエラーを受け取ります。

resolveされた時

const promise = new Promise((resolve) => {
  resolve('成功しました');
}).then(result => console.log(result));
// 成功しました

resolveの場合は、resolveの引数がthenの第一引数に渡ります。
つまり、resolveの引数'成功しました'と言う文字列が、thenの引数resultに引き継がれることになります。

rejectされた時

const promise = new Promise((resolve, reject) => {
  reject('失敗しました');
}).then(
  result => console.log(result), // 実行されない
  error => console.log(error) // rejectの引数が渡される
);
// 失敗しました

一方、rejectの場合は、thenの引数に2つの関数を用意することが必要です。
しかし、実際に実行されるのは2番目の関数です。
rejectの引数'失敗しました‘と言う文字列が、thenの第二引数errorに引き継がれることになります。

resolve・rejectされた時の処理をまとめて書く場合

実際のプログラムでは、resolveされた時とrejectされた時の処理をまとめて書くこともあります。
これらを合わせたthenの使い方も見てみましょう。

const validation = (password) => {
  return new Promise((resolve, reject) => {
    if (password.length >= 10) {
      resolve({
        password: password,
      });
    } else {
      reject('パスワードは10文字以上で入力してください');
    }
  });
};

let password = '123456789';

validation(password).then(
  result => console.log(result), // バリデーションが成功した時の処理(resolve)
  error => console.log(error) // バリデーションが失敗した時の処理(reject)
);
// 'パスワードは10文字以上で入力してください'

ここでは、let password = '123456789'とあえて9文字のパスワードで変数宣言しています。
そのため、if文でパスワードの文字数が10文字以上でない場合のrejectに回り、thenのerrorが返されたということになります。

試しに、パスワードをlet password = 1234567890と10文字に設定し直してみるとどうでしょう。

実行結果

// {password: "1234567890"}

パスワードが10文字以上のため、バリデーションが成功した場合の処理を実行します。

catchメソッド

catchメソッドは、rejectedステータスのPromiseオブジェクトを受け取ります。
かんたんに言うと、エラー処理専用のメソッドです。

実質的には、thenメソッドでrejectedステータスを扱う場合と同じですが、コードが簡潔化されます。

const promise = new Promise((resolve, reject) => {
  reject('失敗しました');
}).catch(error => console.log(error));
// 失敗しました

Promiseチェーン

処理に失敗した時は、catchに書かれた処理を実行することができますが、例えば、その処理の後にまた別の新しい処理を実行したい場合もあるでしょう。

catchメソッドは、thenメソッドと組み合わせて使用することもでき、以下のように処理を制御することができます。

const promise = new Promise((resolve, reject) => {
  reject();
})
  .then(() => {
    console.log('resolve');
  })
  .catch(() => {
    console.log('error');
  })
  .then(() => {
    console.log('resolve again');
  });
// error
// resolve again

rejectされたため、catchに書かれた処理が実行され、その後に2つ目のthenの処理が実行されました。

このように、catchthencatchとメソッドをつなぎ合わせていくことをPromiseチェーンと呼びます。
Promiseチェーンをうまく活用することで、コールバック関数のようにネストが深くならず、複数の非同期処理を制御することができます。
また、どのような処理を行っているのか分かりやすく、可読性も上がります。

まとめ

今回はPromiseを使った非同期処理の方法を解説しました。

// ポイント
* Promiseはステータスによって処理を制御する
* thenメソッドは、fullfilledとrejectedステータスのPromiseオブジェクトを受け取る
* catchメソッドは、rejectedステータスのPromiseオブジェクトを受け取る

Promiseでできることは多岐に渡り、習得するのは容易ではありません。
しかし、より良いコードフローを実現することができるため、少しずつ理解に繋げていきましょう。

合わせて読みたい非同期処理シリーズ

第1回:非同期処理とコールバック関数
第2回:Promise -then・catch(当記事)
第3回:Promise -finally・Promise.all
第4回:Async/Await