非同期同期。 JSDeferred

最近、非同期呼び出しの操作に関するいくつかの記事がhabrに掲載されています( すべての非同期呼び出し非同期呼び出しの同期。WaitSync )。 しかし、綿密に調べてみると、これらの方法ではすべての問題が解決されるわけではないため、アプリケーションの適用範囲はかなり狭くなっています。

しかし、最初に、非同期呼び出しを操作するときに発生するこれらの問題を特定してみましょう。



そして、実験のために、例*を選択します。この例では、ユーザープロファイル、新しいメッセージを要求する必要があり、その後にのみページが表示されます。 同期実行を使用する場合、ソリューションは次のようになります。

var profile = get_profile(user); var msgs = get_msgs(user); render_page({'profile' : profile, 'msgs' : msgs});
      
      





*すべての例は合成であり、本質に焦点を合わせるためにいくつかの点を逃しました



問題は次のとおりです。



1.大きなコードのネスト



この例の非同期バージョンは次のようになります。

 var params = {}; get_profile(user, function(profile){ params['profile'] = profile; get_msgs(user, function(msgs){ params['msgs'] = msgs; render_page(params); }) })
      
      





ネストされたクエリが多数あると、そのようなレコードは読みにくくなり、デバッグが困難になります。



2.並列呼び出し



この例では、すべての呼び出しは順番に行われますが、それらはすべてユーザー引数にのみ関連付けられており、互いに独立しているため、ページ生成を高速化するためにそれらを並行して実行したいと思います。



通常、彼らはこれを行います:

 var params = { 'profile' : null, 'msgs' : null } render_page(){ if (params['profile'] && params['msgs'] !== null){ do_render_page(); } } get_profile(user, function(data){ params['profile'] = data; render_page(); }) get_msgs(user, function(data){ params['msgs'] = data; render_page(); })
      
      





この問題については、「 すべての非同期呼び出しの後」および「非同期呼び出しの同期」で説明しています。 WaitSync



上記のトピックの方法を使用すると、問題の解決策は次のようになります。

 var process = render_page.after('profile', 'new_msgs'); get_profile(user, process.profile); get_msgs(user, process.new_msgs );
      
      







3.エラー処理



get_profile()またはget_msgs()の実行中に発生したエラーまたは例外を処理する必要があるとします



同期コードの場合、すべてが非常に簡単です。

 try{ var msgs = get_msgs(user); var profile = get_profile(user); if (profile){ render_page({'profile' : profile, 'msgs' : msgs}); }else{ redirect_to_login_page(); } }catch(e){ show_error_msg(e); }
      
      







非同期呼び出しの場合、エラーをコールバックのパラメーターとして渡すか、別のコールバックを使用できます。 get_profile()またはget_msgs()内で偶然/意図的に発生する可能性のある例外は、外部から簡単にキャッチすることはできません。



4.拡張性



この問題は、最初の3つの結果として発生します。

最近のコメント、最近のトピックの読み取り、評価の別のリストを一貫して取得したいとします。 その後、最初の段落の例は恐ろしいモンスターに変わります。

 var params = {}; get_profile(user, function(profile){ params['profile'] = profile; get_msgs(user, function(msgs){ params['msgs'] = msgs; get_last_comments(user, function(comments){ params['comments'] = comments; get_last_readed_topics(user, function(topics){ params['topics'] = topics; get_rating(user, function(rating){ params['rating'] = rating; render_page(params) }) }) }) }) })
      
      





エラー処理のためにコールバックを追加すると、おそらくあなたのコードを理解しなければならないプログラマーに呪われます。



私たちを助けるために急いで... JSDeferred



そして、Deferredメカニズムの実装の1つ、つまりJSDeferredに慣れていない人を紹介したいと思います。 このライブラリを使用すると、非同期呼び出しを同期として処理できます。 4つの問題の解決策は次のようになります。



1.大規模なコードのネスト(ソリューション)



コールバックはチェーンに置き換えられます。 チェーンリンクの実行結果は、引数とともに次のリンクに渡されます。

 var params = {}; next(function(){ return get_profile(user) }). next(function(profile){ params['profile'] = profile }). next(function(){ return get_msgs(user) }). next(function(msgs){ params['msgs'] = msgs; render_page(params); })
      
      







2.並列呼び出し(ソリューション)



次のリンクは、すべてのリクエストが並行して結果を返した後にのみ呼び出されます。 引数は、並列実行結果の配列を渡します。

 parallel([ get_profile(user), get_msgs(user) ]). next(function(params){ render_page({'profile' : params[0], 'msgs' : params[1]}) //    // render_page.apply(this, params); })
      
      







3.エラー処理(解決策)



リンクで例外が発生した場合、移動順で次のリンクはエラーと呼ばれます。 パラメーターは、throwを介してスローされるメッセージを渡します。

 var params = {}; next(function(){ return get_profile(user) }). error(function(e){ redirect_to_login_page(); }). next(function(profile){ params['profile'] = profile }). next(function(){ return get_msgs(user) }). error(function(e){ show_error_msg(e); }). next(function(msgs){ params['msgs'] = msgs; render_page(params); })
      
      







4.拡張性(ソリューション)



ここでは、新しいハンドラーステップを追加するためにすべてが明確になっているはずです。新しい要素をチェーンに追加するだけです。



ニュアンス



主な問題の解決策を検討しましたが、今では1つのニュアンスについて検討しました。 非同期関数/メソッドは特別な方法で準備する必要があります。

1. Deferredオブジェクトを返す必要があります

2.チェーンに沿ってさらに移動するには、Deferredオブジェクトのcall()メソッドを呼び出します

3.エラーを生成するには、fail()メソッドを呼び出します。

メソッドの完全なリストはドキュメントにあります



つまり XmlHttpRequestを使用して変更された関数は次のようになります。

 function http_get(url) { var d = Deferred(); var xhr = new XmlHttpRequest(); xhr.open("GET", url, true); xhr.onreadystatechange = function() { if (xhr.readyState != 4) return; if (xhr.status==200){ d.call(xhr.responseText); } else { d.fail(xhr.statusText); } } xhr.send(null); return d; }
      
      







おわりに



単一のAJAXリクエストの場合、Deferredの有用性はかなり疑わしいですが、相互接続された非同期呼び出しを多数使用する場合、またはプロジェクトが成長してより複雑になる可能性がある場合は、Deferredに注意を払うのが理にかなっています。 これは非常に強力なメカニズムであり、リンクの並列/順次実行、エラー処理などを読み取り可能なコードで組み合わせることで、大きなチェーンを構築できます。



推奨読書



1. githubでJSDeferred

2. JSDeferredプロジェクトページ

3. ネストされた非同期呼び出し。 遅延オブジェクトの詳細

4. dojo.Deferred

5. JQuery Deferred / JSDeferred



All Articles