web-dev-qa-db-ja.com

非同期JavaScriptコードの関数型プログラミングパターン

私は自分のプロジェクトの1つで本当に気に入っているパターンを採用しましたが、それは標準的なものになる可能性が高いと思います。ここでそれについて説明し、他のJavaScriptプログラマーがこのタイプの問題をどのように解決するかを皆さんに教えてもらえるかどうかを確認したいと思います。

プロジェクトを始める前に、自分のためにいくつかのルールを設定しました。 1.可能な限り機能的/遅延評価。起動時にたくさんのことをしないでください。 2. promiseを使用します(私はたまたまAngularの$ qを使用していますが、構文はすべてかなり似ています)

簡単にするために、画面を更新して、ユーザー名、ユーザーのいいねの数、現在の友達の数を上部に表示したいとします。これの一部は不自然に思われるかもしれませんが、私には裸です。

クライアント側のコントローラーで、次のようなことを行うクライアント側サービスを簡単に呼び出します。

   function getCurrentUserName() {
       getCurrentUser().then( function(user) {
           return user.name;
       }
   }

   function getCurrentUserLikeCount() {
       getCurrentUser().then( function(user) {
           return user.likeCount;
       }
   }

   function getCurrentUserFriendCount() {
       getCurrentUser().then( function(user) {
           return user.friendCount;
       }
   }

これで、コントローラーにアクセスする複数のUI要素があり、リクエストがどのような順序で来るかを誰が知っているかもしれません。コントローラーにアクセスするサードパーティのウィジェットが存在する可能性があるため、これらのルーチンの1つに対して複数の呼び出しが迅速に行われる可能性があります。

ここで、最初の明らかな問題は、getCurrentUser()が非同期であり、サーバーに対して何らかのajax/rest呼び出しを行うことです。したがって:1)サーバーに対して不要な呼び出しを行う必要はありません。 getCurrentUser()がすでに呼び出されている場合は、再度呼び出す必要はありません(ユーザーがまだログインしているなどの根拠がある場合)。

したがって、getCurrentUser()が結果をキャッシュするのに十分スマートであると仮定しましょう。後続の呼び出しでは、キャッシュされた値が返される場合があります(まだ非同期でwhen()を使用しています)。

2番目の問題は、getCurrentUser()への別の呼び出しがすでに処理されているときにgetCurrentUser()が呼び出されるとどうなるかです。キャッシュはまだ更新されていないため、2番目の呼び出しはキャッシュを認識しないため、サーバーに対して別のHTTP呼び出しを行います。あなたはそれを望まない。したがって、サーバーへの保留中のリクエストがあることを知り、呼び出しを抑制する必要があります。

したがって、私が採用したパターンは、サーバーを呼び出す非同期関数が次の高レベルのアイデアに従うというものです。1.最初の呼び出しである場合、サーバーに呼び出しを発行し、すぐにクライアントにpromiseを返します。また、この約束を守ってください。サーバーが応答したら、promiseを解決しますが、サーバーから返された元の値(キャッシュなど)も保持します

  1. 最初の通話でない​​場合は、最初の通話が完了したかどうかを確認します。最初の呼び出しが既に完了している場合は、サーバー(キャッシュなど)から返された値を返します。もちろん、クライアントはpromiseを期待しているので、when()を使用してこれを行います。

  2. 最初の呼び出しではないが、最初の呼び出しがまだ完了していない場合は、最初の呼び出しによって返されたキャッシュされたpromiseをクライアントに返します。その後、約束が解決されると、すべてのクライアントに通知されます。

実際、angular $ q(およびおそらく他の)でも機能するのは、過去に返された後でも元のプロミスを再利用することです。そうすれば、データのキャッシュも回避できます。 promiseにはサーバーから返されたデータが含まれているためです。

より良い用語がないため、私はこれをすべてスロットルと呼んでいます。

次の関数は、既存の非同期関数をラップし、自動的にスロットルする新しい関数を作成します。

    // This routine makes a custom throttle wrapper for the async-function that's passed in.
    // A throttle function, when called, calls an async function (one that returns a promise)
    // in such a way to ensure the async function is only called when it's not already in progress.
    // in which case rather than calling it again, this routine returns the original promise to the caller.
    // If it's already in progress, then the original promise is returned instead.
    var makeThrottleFunction = function (asyncFunction) {
        var asyncPromise = null;
        return function (asyncInParam) {
            if (!asyncPromise) {
                var asyncDeferred = $q.defer();
                asyncPromise = asyncDeferred.promise;
                asyncFunction(asyncInParam).then(function (asyncOutParam) {
                    asyncDeferred.resolve(asyncOutParam);
                }).catch(function (parameters) {
                    return $q.reject(parameters);
                }).finally(function () {
                    asyncPromise = null;
                });
            }
            return asyncPromise;
        };
    };

使用例:

var getCurrentUser = makeThrottleFunction(function() {
    // async call to server to get data
    // return promise..
});

私はこのアプローチについて専門家が何を言わなければならないか知りたいです。代替案とは何かなど、アプリのいたるところで使用しましたが、非常にうまく機能しています。また、これなしでは実現できないと思います。そのため、問題が発生します。このようなものがフレームワークにまだない場合は、すでにこの非常に正確な設定を行っている設定やライブラリなど、明らかなものがない可能性があります。だから私はいくつかのフィードバックを得たいです。

この一般的な問題を解決する方法について他の人からの意見を探していますが、これがQ&Aであることを満足させるには、次のいずれかが適切だと思います。1.このタイプのことを行うライブラリを指す。 2.これを行う簡単な方法があることを指摘します。

6
zumalifeguard

私はこの手法の公開された実装を知りませんが、それはプロミスを使用するかなり標準的な方法です。関数型プログラミングの構成可能性、再利用可能性、および拡張性の興味深い副作用は、多くの場合、期待するものをライブラリーで見つけられないことです。

構成しにくいパラダイムでは、他の関数全体でサポートを織り込む必要があるため、スロットル関数のようなことを行うにはライブラリで必要サポートする必要があります。関数型コードでは、プログラマは自分で1回記述するだけで、どこでも再利用できます。

このようなものを書いた後、誰と共有しますか?小さすぎて独自のライブラリにはなりません。それは少し具体的すぎて、promiseライブラリで肥大とは見なされません。おそらく、ある種のpromiseユーティリティライブラリですが、promiseに漠然と関連する他の関数とのまとまりのないモジュールに含まれるため、見つけるのが難しくなります。

私によく起こることは、10分かそこらを検索し、30分しかかからないので自分で書くことです。私はそれを他のオッズと一緒に保持する「ユーティリティ」ファイルに入れて、短いが再利用性は高いが実際にはどこにも適合しない関数を終了します。それから6か月後、奇妙な名前のコミュニティが管理するライブラリで偶然偶然それが起こります。

私のポイントは、関数型プログラミングで、かなり「標準」に感じられるものの既存の実装を見つけることができないことは、他の人々が優れた方法でそれを行っていること、またはあなたが最初に思いついたことの兆候ではありませんそれと。これは、短い再利用可能なコードを検索で見つけやすい方法で共有することの難しさの兆候にすぎません。

5
Karl Bielefeldt