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

そこで今回は「finally」「Promise.all」について解説します。

finallyメソッド

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

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

イメージしやすいように、thenとfinallyを比較してみましょう。以下は、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

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を扱う
//'結果に関係なく処理'
//'失敗'

finally-catch

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

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

ここまでのおさらい

then/catch/finallyメソッドの主な役割を追ってきましたが、結局どれを使ったら良いのか混乱してしまう場合、まずは以下を意識すると良いでしょう。

・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になった場合、そこで処理が終了し、rejectedの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.all

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

まとめ

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

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

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

非同期処理のおさらい