6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2022年2月11日 非同期処理:コールバック/Promise/Async - Promiseと例外 (JavaScript Primer)

Promiseではコンストラクタの処理で例外が発生した場合に自動的に例外がキャッチされます。 例外が発生したPromiseインスタンスreject関数を呼び出したのと同じように失敗したものとして扱われます。 そのため、Promise内で例外が発生するとthenメソッドの第二引数やcatchメソッドで登録したエラー時のコールバック関数が呼び出されます。

function throwPromise() {
    return new Promise((resolve, reject) => {
        // Promiseコンストラクタの中で例外は自動的にキャッチされrejectを呼ぶ
        throw new Error("例外が発生");
        // 例外が発生すると、これ以降のコンストラクタの処理は実行されません
    });
}

throwPromise().catch(error => {
    console.log(error.message); // => "例外が発生"
});

補足と復習

errorをキャッチするには、catchメソッドを利用するか、thenを使用する場合は第一引数にundefinedを指定する必要があります。

このようにPromiseにおける処理ではtry...catch構文を使わなくても、自動的に例外がキャッチされます。

Promiseの状態

  • Fulfilled

resolve(成功)したときの状態。このときonFulfilledが呼ばれる

  • Rejected

reject(失敗)または例外が発生したときの状態。このときonRejectedが呼ばれる

  • Pending

FulfilledまたはRejectedではない状態 new Promiseでインスタンスを作成したときの初期状態

上記の3つの状態が存在し1度 Fulfilledかrejectになったものの状態は変化しないため、その事をsettled(不変)と呼びます。

  • settled

どちらかの状態になって事を意味します。

※ settledを和訳すると、『落ち着いた』という意味だそうです。

以下、状態が変化しない事を示したコード例です。

const promise = new Promise((resolve, reject) => {
    // 非同期でresolveする
    setTimeout(() => {
        resolve();
        // すでにresolveされているため無視される
        reject(new Error("エラー"));
    }, 16);
});
promise.then(() => {
    console.log("Fulfilledとなった");
}, (error) => {
    // この行は呼び出されない
});

Promiseコンストラクタ内でresolveを何度呼び出しても、そのPromiseインスタンスの状態は一度しか変化しません。 そのため、次のようにresolveを何度呼び出しても、thenで登録したコールバック関数は一度しか呼び出されません。

この引用はFulfilledになったものに再度resolveをしている例です。先ほどと同じように1度決まった状態は変化しないので、Fulfilledになったものに対してresolveは1度目の処理しか実行されないというものです。

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve();
        resolve(); // 二度目以降のresolveやrejectは無視される
    }, 16);
});
promise.then(() => {
    console.log("最初のresolve時に一度だけ呼ばれる");
}, (error) => {
    // この行は呼び出されない
});

このようにPromiseインスタンスの状態が変化したときに、一度だけ呼ばれるコールバック関数を登録するのがthenやcatchメソッドとなります。

またthenやcatchメソッドはすでにSettledへと状態が変化済みのPromiseインスタンスに対してもコールバック関数を登録できます。 状態が変化済みのPromiseインスタンスを作成する方法としてPromise.resolveとPromise.rejectメソッドがあります。

次回から状態が変化済みのPromiseインスタンスを作成する方法を学んでいきます。

検証

最初に使用されていたthrowPromise()関数を const yanoに代入して出力したプロパティを確認してみました。

function throwPromise() {
    return new Promise((resolve, reject) => {
      reject();
    });
}

throwPromise().catch(error => {
    console.log(error.message); // => "例外が発生"
});
const yano = throwPromise()
console.log(yano);

上記のようにreject()を実行すると、ログに出力したPromiseオブジェクトの内部プロパティのPromiseStateが"rejected"となりました。 image

resolve()を先に実行した場合はPromiseStateが"fulfilled"となり、 resolve()とreject()をどちらも実行しなかった場合はPromiseStateが"pending"と出力されました。

まとめ

  • Promiseには常に内部的に3つの状態が存在する。(PromiseStateという内部プロパティに登録されます。)
  • Promiseインスタンスの状態は作成時にPendingとなり、一度でもFulfilledまたはRejectedへ変化すると、それ以降状態は変化しなくなる。
  • FulfilledまたはRejectedとなり不変になった状態であることをSettledという。

復習

プロトタイプメソッドとインスタンスメソッド

インスタンス間の共有 インスタンスから呼び出せる
プロトタイプメソッド(インスタンスメソッド) ⭕️ ⭕️
インスタンスメソッド(コンスタラクタ関数内のthisにメソッドを定義した場合) ⭕️

インスタンス間で共有できるメソッドをプロトタイプメソッドと呼びますが、インスタンスオブジェクトにメソッドを定義すると、共有できるメソッドではなくなってしまいます。

違いを簡単に説明

  • プロトタイプメソッド(インスタンスメソッド) class構文内にメソッドの短縮記法で定義したメソッドです。
  • インスタンスメソッド(プロトタイプメソッドではない) classのコンストラクタ関数内のthisに定義したメソッドです。

参考

JS primer Promiseと例外