JSのPromiseとJQueryPromiseを雰囲気で理解した

雰囲気で理解したのでメモです。

JSの世界だとPromiseがあって、基本的に失敗/成功で処理を切り分けていく事ができる。 よく使われる方法だと、非同期処理を同期的に使いたいときに使われる。 例えばAというエンドポイントにアクセスした後に取得した情報で、Bエンドポイントにアクセスする必要がある場合、何も考えずにjsで書いてしまうとAの処理が終わる前にBにアクセスに行く可能性がある。

Promiseを使うとAが終わるまでBが処理されないので同期的に使うことが可能。

ちなみにJQueryの世界にはJQuery Promiseというものがある。JSのnativeなPromiseとの使い分けはこのStackOverflowが詳しい。

stackoverflow.com

要約するとPromiseサポートしていないブラウザでPromise使いたい場合はJQueryPromiseを使おうという世界観。 JQuery使っていないけど昔のブラウザでもPromise使いたいんじゃというときはcore-jsなどを使うとよしなに変換してくれるらしい。

ネイティブのPromiseだと最近はもうasync functionの内部処理か、複数一気動かすみたいなときにPromise.allを打つくらしか手で実行することはない気がする。 反面JQueryPromiseを使う場合、async functionに対応していないブラウザで動かすのが目標にある場合があるので、async functionみたいなリッチな構文を使わずに気合でPromiseを処理する必要がある。

JQueryPromiseを使う場合はDefferedオブジェクトのAPIを利用することになる。 ちょっとわかり付いらいのだけど、Promiseのresolve/rejectはPromiseに生えていそうなのだけど違っていて、Promiseオブジェクトを作ることができるDefferedオブジェクト側のAPIとして定義されている。 Defferedオブジェクトのresolve/rejectを実行することで、ネイティブのresolve/reject処理を実現している世界観。

対してPromiseがresolve/rejectした場合に処理を分岐させる、ネイティブのthenとerror処理に相当するのは、Promiseオブジェクトのthenとfailを呼ぶことで実現している。 分岐させたい処理を発生させる関数がJQueryPromiseを返すので、ネイティブのPromiseチェーンのような書き心地で書く事ができる。 わかりやすいのだとJQuery.ajaxはJQueryPromiseを返すのでそこで分岐させることが可能。

JQueryPromiseがRejectedになった場合、fail処理を受け持つのは、ネイティブのPromiseと同様に一番近いRejectハンドリング部分が受け取る。 ネイティブのPromiseの場合はcatchが受け取っていたが、JQueryPromiseの場合はfailが相当する。resolve/rejectどっちでも処理を継続させたい場合はalwaysを指定する。

const defer = $.Deferred();

ちなみにTypeScriptを使っているケースで、resolve時に値を返したい場合は、返り値の型を<>の中に指定する。

const defer = $.Deferred<HTMLHtmlElement>();

テストを書くときに強制的にPromiseをrejectしたい!!!と言うときはどうするかというと、強制的に$.Deferred().reject()を呼び出すとrejectされる。 当然resolveを強制させたいときも$.Deferred().resolve()するとよい。

例えば、sinonを使ってmockしたメソッドが返すJQueryPromiseをrejectした状態にするには次のように書く。

const stubFunction = sinon.stub(object, 'stubFunction');
stubFunction.returns($.Deferred().reject());

こうするとstubFunctionrejectされた際の挙動を取るので、例えばエラーメッセージの内容を検証したい場合は次のように続けられる。 この例ではrejectされたらfailに処理が落ちるので、errorにエラーメッセージが代入されて検証可能になる。

let error : any;
const expectError = new Error("エラー");

object.stubFunction().fail((_error) => {
   error = _error;
});

assert.equal(error.message, expectError.message, 'rejectするとエラーが帰ってくる');

こうみるとJQueryでPromiseをするには$.Deferred()を取り回せばええやんという感じになる。 とはいえぐぐると出てくるJSのPromiseの使い方とは別なので慣れるまでは大変そう。。。

参考

jsprimer.net

azu.github.io

developer.mozilla.org