Promiseには、もっとも重要で基本的なメソッドとしてthenとcatchがあります。
これらだけでも、非同期処理を制御することができますが、さらに処理を継続するためのメソッドについても知っておくと便利です。

そこで今回は、Promiseで使われるfinallyPromise.allメソッドについて解説します。

finallyメソッド

finallyメソッドとは、処理の成功・失敗に関わらず、その先の処理を継続して行うメソッドです。Promiseチェーンのさいごに必ず呼び出したい処理などを定義することができます。

thenメソッドとの違いは、Promiseの処理結果を判断する必要がない点です。
成功したか失敗したかは関係なく、Promiseが確定された段階で処理を行うということになります。

イメージしやすいように、thenとfinallyを比較してみましょう。

Promiseが失敗した場合

以下は、Promiseが失敗した場合の例です。

// thenの場合
const promise = new Promise((resolve, reject) => {
  const something = true;
  if (!something) {
    resolve('成功');
  } else {
    reject('失敗')
  }
}).then(
    result => console.log(result),
    error => console.log(error)
  );
// '失敗'

thenメソッドで、処理が成功した場合と失敗した場合の処理を記述するためには、引数を2つ用意する必要があります。
この場合、処理が失敗したため、rejectの引数がthenの引数errorに渡っています。

// finallyの場合
const promise = new Promise((resolve, reject) => {
  const something = true;
  if (!something) {
    resolve('成功');
  } else {
    reject('失敗');
  }
}).finally(() => console.log('結果に関係なく処理'));
// '結果に関係なく処理'

一方、finally(f)は、then(f, f)よりも簡潔に記述することができます。
finallyメソッドは、処理が成功したか失敗したか判断しないため引数を取りません。

この場合、処理は失敗していますが、finallyメソッドでの処理は実行されます。

finally – then

以下を見ると、finallyメソッドがどのような動きをしているか分かります。

const promise = new Promise((resolve, reject) => {
  const something = true;
  if (!something) {
   resolve('成功');
  } else {
   reject('失敗');
  }
})
  .finally(() => console.log('結果に関係なく処理'))
  .then( // resolve, またはrejectを扱う
    result => console.log(result),
    error => console.log(error)
  );
// '結果に関係なく処理'
// '失敗'

finallyメソッドの後に、thenメソッドで処理を継続してみると、thenメソッドはPromiseの成功または失敗の結果を扱うことが分かります。
つまり、finallyは次のハンドラにそのまま処理結果を渡しているということです。

finally – catch

では、catchメソッドで繋げるとどうでしょうか。

const promise = new Promise((resolve, reject) => {
  const something = true;
  if (!something) {
    resolve('成功');
  } else {
    reject('失敗');
  }
})
  .finally(() => console.log('結果に関係なく処理'))
  .catch(error => console.log(error)); // rejectを扱う
// '結果に関係なく処理'
// '失敗'

catchは、エラーオブジェクトを扱うメソッドです。
この場合も、finallyが次のハンドラにそのままエラーオブジェクトを渡していることが分かります。

以上のことから、finallyメソッドは、Promiseの結果を元に処理を行う手段ではないと言えます。

ここまでのおさらい

これまで、thencatchfinallyメソッドの主な役割を追ってきましたが、ここで一旦おさらいしておきましょう。
どれを使ったら良いのか混乱してしまう場合、まずは以下を意識すると良いでしょう。

  • thenメソッド:Promiseが成功した場合の処理
  • catchメソッド:Promiseが失敗した場合の処理
  • finallyメソッド:Promiseチェーンのさいごに必ず呼び出したい処理

Promise.allメソッド

Promise.allメソッドとは、指定したすべてのPromiseオブジェクトに対して処理を実行するメソッドです。
すべてのPromiseオブジェクトがfulfilledステータスになると、それぞれの結果の値を集めた配列を返します。

指定した複数のPromiseオブジェクトを配列として引数に取り、thenメソッドで繋ぎます。

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

const promiseB = new Promise((resolve, reject) => {
  resolve('string')
});

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

Promise.all([promiseA, promiseB, promiseC]).then((results) => {
  console.log(results);
});
// [123, 'string', true]

上記ではrejectを使用していないことに注意してください。
Promise.allメソッドは、いずれかのステータスがrejectedになった場合、そこで処理が終了し、rejectされたPromiseオブジェクトが返されます。

以下は、promiseCの処理が失敗した例です。

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

const promiseB = new Promise((resolve, reject) => {
  resolve('string')
});

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

// promiseCがrejectされたため、実行されない
Promise.all([promiseA, promiseB, promiseC]).then((results) => {
  console.log(results);
});

// rejectされたオブジェクトを返す
.catch(error => console.log(error));
// false

では、どのような時にPromise.allメソッドが使われるのでしょうか。
例えば、すべての処理が適切に終了したことを確認したうえで、新しい処理を行いたい場合です。

const promiseA = new Promise((resolve) => {
  resolve()
}).then(() => console.log('処理A完了'));

const promiseB = new Promise((resolve) => {
  resolve()
}).then(() => console.log('処理B完了'));

const promiseC = new Promise((resolve) => {
  resolve()
}).then(() => console.log('処理C完了'));

Promise.all([promiseA, promiseB, promiseC]).then(() => {
  console.log('すべての処理が完了しました');
});
// '処理A完了'
// '処理B完了'
// '処理C完了'
// 'すべての処理が完了しました'

このような処理は、メールアドレスやユーザー名の登録を行うためのバリデーション処理時に役に立ちます。
すべて適切な値であるか審査に通ったら、登録を完了させるような場合です。

まとめ

今回は、Promiseのfinally、Promise.allメソッドについて解説しました。

// ポイント
* finallyメソッドは、Promiseオブジェクトの結果に関わらず処理を実行する
* Promise.allメソッドは、指定したすべてのPromiseオブジェクトがfullfilledステータスになれば処理を実行する

特にPromiseチェーンを扱っていく場合、メソッドの使い分けが大切なため、各役割を理解していきましょう。

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

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