Promise.prototype.finally()

finally()Promise のメソッドで、プロミスが決定したとき(履行されたか拒否されたかのどちらか)に呼び出される関数を準備します。直ちに同等の Promise オブジェクトを返すため、プロミスの他のメソッドを連鎖呼び出しすることができます。

これによって、プロミスの then() ハンドラーと catch() ハンドラーでコードが重複することを避けることができます。

試してみましょう

構文

finally(onFinally)

finally(() => {
  // プロミスの決定(履行または拒否)後に実行されるコード
})

引数

onFinally

Promise が決定したら呼び出される関数 (Function) です。このハンドラーは引数を取りません。

返値

同等の Promise を返します。ハンドラーがエラーを発生したり、拒否されたプロミスを返したりした場合、代わりに finally() が返すプロミスがその値で拒否されます。そうでなければ、ハンドラーの返す値は元のプロミスの状態に影響しません。

解説

プロミスが決定した後、結果に関わらず何らかの処理や後始末を行いたいなら、finally() メソッドは役立ちます。

finally() メソッドは then(onFinally, onFinally) の呼び出しとよく似ていますが、いくつかの点が異なります。

  • 関数をインラインで作成する場合、関数を 2 度宣言するか、変数を作成するかのどちらかで、一度に渡すことができます。
  • onFinally コールバックは一切引数を受け取りません。この用途では、拒否された理由や履行された値などを提供する必要がなく、それらを気にしないときに適しています。
  • finally() の呼び出しは通常透過的で、元のプロミスの最終的な状態を変更することはありません。ですから例えば、
    • Promise.resolve(2).then(() => 77, () => {}) が最終的に 77 の値で履行されるプロミスを返すのとは異なり、Promise.resolve(2).finally(() => 77) は最終的に 2 の値で履行されるプロミスを返します。
    • 同様に、Promise.reject(3).then(() => {}, () => 88) が最終的に 88 の値で拒否されるプロミスを返すのとは異なり、Promise.reject(3).finally(() => 88) は最終的に 3 の値で拒否されるプロミスを返します。

メモ: finally コールバックの中で throw (あるいは拒否されたプロミスを返すこと)しても、返されたプロミスは拒否されます。例えば、 Promise.reject(3).finally(() => { throw 99; })Promise.reject(3).finally(() => Promise.reject(99)) はどちらも 99 という理由をつけて、返ってきたプロミスを拒否することになります。

catch() と同様に、 finally() は内部的に呼び出されたオブジェクトの then メソッドを呼び出します。もし onFinally が関数でない場合、 then()onFinally を両方の引数として呼び出されます。これは Promise.prototype.then() にとって、有益なハンドラーが添付されないということを意味します。そうでなければ、then() は内部で作成された 2 つの関数で呼び出され、以下のような振る舞いをします。

警告: これは説明用のものであり、ポリフィルではありません。

promise.then(
  (value) => Promise.resolve(onFinally()).then(() => value),
  (reason) =>
    Promise.resolve(onFinally()).then(() => {
      throw reason;
    }),
);

finally()then() を呼び出すので、サブクラス化に対応しています。さらに、上記の Promise.resolve() の呼び出しに注目してください。実際には、 onFinally() の返値は Promise.resolve() と同じアルゴリズムで解決されますが、解決されたプロミスを構築するために使用される実際のコンストラクターはサブクラスとなります。 finally() はこのコンストラクターを promise.constructor[@@species] で取得します。

finally() の使用

let isLoading = true;

fetch(myRequest)
  .then((response) => {
    const contentType = response.headers.get("content-type");
    if (contentType && contentType.includes("application/json")) {
      return response.json();
    }
    throw new TypeError("Oops, we haven't got JSON!");
  })
  .then((json) => {
    /* さらに JSON を処理します */
  })
  .catch((error) => {
    console.error(error); // この行は、 console = {} の場合など、例外が発生する可能性がある
  })
  .finally(() => {
    isLoading = false;
  });

仕様書

Specification
ECMAScript Language Specification
# sec-promise.prototype.finally

ブラウザーの互換性

BCD tables only load in the browser

関連情報