今日は、 約束とコードの再利用についてお話したいと思います。
偶然にも、これら2つのことが何度か一緒になって非常に役立ったので、それがどうだったかを共有したいと思います。

教訓は単純です:まだプロミスを使用していないなら、それを始めてください!
更新:pragmadashがコメントに正しく記載されているように、この記事はjquery.Deferredについてのものであり、厳密には 約束ではありません 。 記事の文脈では、この違いは無視できますが、約束にあまり詳しくない人を誤解させないために、この発言は必要です。
このトピックについては、2つの記事( one 、 zero )で既に簡単に説明しています 。 実際、この記事は前の記事の最後のセクションになるはずでした。 しかし、閲覧室は重くて大きいため、2つに分割することにしました。
ここでは、彼らがどのように私たちを救ったかについての本当の話をいくつかお話しします。
小さな序文:私たちのアプリケーションの1 つでは 、バックエンドへのすべてのリクエストは、中心点-すべてのサービスが継承するベースモジュールのcallServiceメソッドを介して送信されます。 このメソッドは、入力として要求パラメーターを受け入れ、それらを正規化し、呼び出し元がサブスクライブできるプロミスを返します。 このようなもの:
callService: function (settings) { // var callSettings = { type: settings.method || ETR.HttpVerb.POST, contentType: 'application/json; charset=utf-8', dataType: settings.dataType || undefined, url: settings.relativeUrl ? ETR.serviceRootURL + settings.relativeUrl : settings.url, data: settings.data, context: settings.context || this.requestContext, beforeSend: settings.beforeSend || undefined }; // return jquery.ajax(callSettings); }
そして、これはそのような組織が私たちを助けた方法です。
ケース1.破棄されたオブジェクトへのアクセス
問題:
前の記事で説明したアプリケーションを作成したときに、SPAとして実装しました。 したがって、メモリのクリアに非常に一生懸命に取り組む必要があり、文字通りすべてに廃棄パイプラインがありました 。 同時に、リクエストがページからサーバーに送信され、ユーザーが突然ページを離れる(たとえば、間違ったメニュー項目を突いた)状況が発生する可能性があります。 現在のウィンドウのビューモデルは破棄パイプラインを渡し、ユーザーは別のページに移動します。
そして、ここでリクエストの結果がサーバーから返されます。
そのような状況で何が起こるかについて話すのは無意味です。何でも起こります。 起こった最高のことは間違いであり、何かが間違っていることが明らかになりました。
解決策:
上記のcallServiceメソッドには、数行のコードが追加されています。 簡単に言えば、彼らの本質は、私たちがすべての約束を思い出したということです。 完了するか、サービスのdisposeメソッドを呼び出すと、再び忘れていました。
navThrottleKey: 'Any abracadabra. Guid for example or some string like this.', callService: function (settings) { ... var promise = jquery.ajax(callSettings); var requestId = ++requestCounter; this.rejectsOnDispose[requestId] = promise; promise.always(_.bind(function () { delete this.rejectsOnDispose[requestId]; }, this)); return promise; } dispose: function () { for (var indexer in this.rejectsOnDispose) { this.rejectsOnDispose[indexer].abort(this.serviceThrottleKey); delete this.rejectsOnDispose[indexer]; } this.rejectsOnDispose = null; this.requestContext = null; }
出来上がり。 数十行のコードがアプリケーション全体の問題を解決しました。
追加のコメントフィールドに注意してください
this.serviceThrottleKey
これは、記述された状況を「識別」し、 failure promiseメソッドが呼び出されたときに必然的に開始するフェイルパイプラインハンドラーを呼び出さない一意の行であることが保証されています。 つまり、ユーザーのエラーメッセージをUIに表示しないように、障害をインターセプトしてジャムします。
ケース2.ユーザビリティ
問題:
サービスへのある種の呼び出しに時間がかかることがあります。 そして、実行されるまで、ユーザーに何もさせることはできません(たとえば、初期データを読み込んでいるとき)。 同時に、待機する必要があるメッセージをユーザーに表示し、画面をロックします。
また、呼び出しに時間がかかることがあり、キャッシュから)迅速に行われることもあります。 このような場合、疲れてしまうため、すぐに表示されたり消えたりするメッセージをユーザーに表示しないことをお勧めします。
解決策:
解決策はまだcallServiceメソッドにあります。 そこにさらに5行のコードを追加します(コードを読みやすくするために、前の行を削除します)。
callService: function (settings, statusMessage) { ... var sid = statusMessage ? statusTracker.trackStatus(statusMessage) : null; var promise = jquery.ajax(callSettings); promise.done(_.bind(function () { if (sid !== null) { statusTracker.resolveStatus(sid, ETR.ProgressState.Done); } }, this)).fail(_.bind(function () { if (sid !== null) { statusTracker.resolveStatus(sid, ETR.ProgressState.Fail); } }, this)); return promise; }
一番下の行は簡単です:statusMessage文字列がパラメーターとして渡された場合、この呼び出しは呼び出されたときにメッセージを表示することを望みます。
これを示します:
statusTracker.trackStatus(statusMessage)
このように非表示にします:
statusTracker.resolveStatus(sid, ETR.ProgressState.Fail);
statusTrackerオブジェクトは 、次のロジックに従って機能します。
- trackStatusメソッドを呼び出すとき、メッセージは表示されませんが、一意の番号を割り当てて返します( setTimeoutを呼び出して返される番号を使用します)。
- 特定のタイミング(300ミリ秒)の後、 resolveStatusコマンドが到着しなかった場合、このメッセージの表示を開始し、画面をロックします。
- resolveStatusコマンドが到着すると、ステータスアイコンを変更(成功または失敗)し、アニメーションモードでメッセージを非表示にします。 これにより、遅延時間(300ミリ秒)が要求の実行時間と実質的に一致した場合に、「点滅」画面が表示されなくなります。
次のようになります。

ケース3.サービスへの二重呼び出し
問題:
ページ上の複数のオブジェクトが同じデータを要求する場合があります。 同じデータのためにサーバーに行きたくありません(また、上記と同じステータスを表示します)。 何かを考えてみましょう。
解決策:
ソリューションは同じcallServiceメソッドにあります。 ポイントは簡単です。 特定のクエリが複数回呼び出される可能性があることを判断し、そうすることで同じ結果を返すことで満足できるのは、呼び出し元の仕事です。 callServiceメソッドをさらに12行追加します。
callService: function (settings, statusMessage, singletKey) { … if (singletKey) { var singlet = wcfDispatcherDef.singletsCalls[singletKey]; if (singlet) { var singletResolver = jquery.Deferred(); singlet.done(function () { singletResolver.resolveWith(callSettings.context, arguments); }).fail(function () { singletResolver.rejectWith(callSettings.context, arguments); }); return singletResolver; } } if (singletKey) { wcfDispatcherDef.singletsCalls[singletKey] = promise; } … }
注: 参照型の場合、多くの明白でない問題をキャッチできるため、このアプローチは慎重に使用するのが最適です。 潜在的な問題を解決するために、パフォーマンスが許せば、 jquery.extendをdeepフラグまたは通常のJSON.parse(JSON.stringify())とともに使用できます。
ケース4.データのキャッシュについて
問題:
おそらく、ここでは素晴らしい説明は必要ありません。 一定時間、クライアントにデータをキャッシュする必要があります。
解決策:
ソリューションは、サービスの基本クラスにあるメソッドでした。 入力では、 callServiceと同じコントラクトの関数と、パラメーターをキャッシュします。 サービスを呼び出す前に、キャッシュを検索するラッパー関数を返します。
wrapWithCache: function (fn, cacheKey, expirationPolicy) { return _.wrap(fn, function (initialFn) { var cacheResult = cacheService.getCacheValue(cacheKey); if (cacheResult !== undefined) { var q = jquery.Deferred(); _.delay(function (scope) { q.resolveWith(scope, [cacheResult]); }, 0, this.requestContext || this); return q; } else { return initialFn.apply(this, Array.prototype.slice.call(arguments, 1)) .done(_.bind(function (result) { cacheService.setCacheValue(cacheKey, result, expirationPolicy); }, this)); } }); }
注1 :実際、jquery.ajaxを使用する場合は、 アンダースコア遅延 (またはネイティブsetTimeout)を使用する必要はありません。処理後に宣言されたjquery.Defferedのすべてのハンドラーが呼び出されるためです。 ただし、本質的に非同期の作業モデルに違反しないでください。したがって、_。delayを使用します。
結果として、ある種の呼び出しをキャッシュしたい場合、これは、例えば:
{ return this.callService({ method: ETR.HttpVerb.POST, url: this.someServiceUrl + '/getSomeList', data: request }).fail(this.commonFaultHandler); }
次に、サービスに1行のコードを追加します。
this.getSomeList = this.wrapWithCache(this.getSomeList, 'some-cache-key', moment().add(30, 'm'));
注2:前のストーリーで述べた問題もこれに当てはまります。参照型には注意してください。
ケース5.エラー処理について
問題:
どういうわけか物語が起こった:一度アプリケーションがあれば、それはうまく生き、文句を言わなかった。
そして、希望が生じました-一部のユーザーは別のユーザーのデータを表示できるはずです。 同時に、ユーザーがここに表示するものは何もありませんが、ユーザーが誰を表示するかを選択すると、間違いなく表示されます。 サーバーでは、これはすべて簡単に決定されました。 何かを見る権利を持たない一般ユーザーは403 Forbiddenエラーを受け取り、結果は次のようになります。

厳しいユーザーの場合、結果は次のようになります。

選択後、ユーザーには特定のトークンが割り当てられ、...はい、問題ではありません。 選択の対話を表示する方法についての物語。
アプリケーション全体...すでに作成され、実稼働でリリースされています...
解決策:
クライアント上に共通のエラー処理パイプラインが既にあることが保存されました。 その本質は、発生したエラーを処理できるかどうかを確認するためのハンドラーの配列にあります。 特定の可能性がある場合、メソッドはtrueを返し、残りは呼び出されません。 それができなかった場合、falseを返し、エラーを処理する試みが続行されます。
注:完全に特定の場所で非常に特定のエラーを処理する必要がある場合、共通処理パイプラインの呼び出しは、特定のサービスの失敗ハンドラーの仕事です。 彼は、処理するために呼び出されたものをエラーで認識しない場合、これを行います。
問題を解決するために必要なのは、アプリケーションの起動時にユーザー権限をチェックするメソッドを書くことだけでした。 そして、その場合、私は別の403ステータスハンドラーをパイプラインに挿入しました。これにより、大切なダイアログが表示されます。
数時間の作業で完了です。
それを読んでくれたみんなに感謝します。 じゃあね!