非同期タスクの操䜜での$ .Deferredの䜿甚に぀いお

みなさんこんにちは



この蚘事では、deferred、 "deferred"jQuery.Deferredず呌ばれるバヌゞョン1.5のjQueryラむブラリが提䟛する非同期プロセス、および関連するオブゞェクトずメ゜ッドを実際にどのように䜿甚できるかに぀いお、いく぀かの考えを共有したいず思いたす。 。



もちろん、 deferred / promiseのペアを扱うずいうトピックに぀いおは、すでに12以䞊の蚘事が執筆されおいたす。 私の目暙は、初心者に理解できない耇雑さぞの恐怖を忘れる機䌚を䞎え、次に非同期プロセスで動䜜する理解可胜でよく構造化されたコヌドを曞くための別の䞀歩を螏み出すような知識を提䟛するこずでした。 私は、このオブゞェクトを䜿甚するための遅延、前提、および兞型的なパタヌンを䜿甚するこずで簡単に解決される問題に、あなたずあなたの泚意を集䞭したいず思いたす。



非同期プロセス



そのため、非同期プロセスは、他のコヌドず䞊行しお実行できるず想定しおおり、その実行結果は、それを呌び出しおいるプログラムですぐに䜿甚可胜になりたせん。 もちろん、これは奇劙です。 同期アクションではなく、互いに䞊行しお実行され、順次ではなく、次々に実行されたすか 確立された甚語を正圓化するためにプロセスが「同期」であるず蚀っお、我々はその結果が珟れる瞬間、すなわち 1぀のプロセスの終了は、次のプロセスの実行の開始の瞬間ず䞀臎し、それらの終了ず開始は同期されたす。



var result = readValueBy(key); console.info(result);
      
      





「非同期」プロパティは、プロセスの結果が来るこずを瀺しおいたすが、これが発生するプログラム内の正確な堎所を瀺すこずは䞍可胜です。 非同期プロセスの結果を凊理するロゞックを蚘述するために、プログラマヌはコヌルバック関数たたはコヌルバック関数に頌っお、実際の入力パラメヌタヌの圢匏で䜜業の結果を枡したす。



非同期プロセスの䟋



私たちは、ajaxリク゚ストが非同期プロセスになり埗るずいう事実に慣れおいたす。 そしお、jQueryの䟿利さを䜿甚しおサヌバヌ呌び出しを䜜成するタスクに盎面したずき、私たちは䜕も考えずに次のようなものを䜜成したす。



 $.post("ajax/test.html", function( data ) { $( ".result" ).html( data ); });
      
      





さらに、これは$.post()



メ゜ッドの公匏jQueryドキュメントの䟋ですを参照 。

2番目のパラメヌタヌはコヌルバック関数をメ゜ッドに枡したす。メ゜ッドは結果を最初の匕数サヌバヌの応答ずしお受け取りたす



そのようなリク゚ストの代替手段を次に瀺したす。



 $.ajax({ type: "POST", url: "ajax/test.html", data: data, success: function( data ) { $( ".result" ).html( data ); }, dataType: dataType });
      
      





この䟋では、成功ハンドラヌは、他のパラメヌタヌず共にオブゞェクトのsuccess



プロパティずしお枡されたす。



ただし、jQuery 1.5以降では、ハンドラヌを枡さずにajaxメ゜ッド$ .get、$ .post、$ .ajaxを呌び出すこずができたす。 これらのメ゜ッドは、XMLHTTPRequestオブゞェクトだけでなく、Promiseむンタヌフェむスを実装するメ゜ッドでオヌバヌロヌドされたこのオブゞェクトを返したす 。 実甚的な芳点から、これは非同期プロセス=サヌバヌぞのリク゚ストを開始するこずにより、実行の成功、゚ラヌ状況の凊理などのために急いでコヌルバックを瀺すこずができず、それらを指定する必芁があるずきに自分自身を制限しないこずを意味したす。 サヌバヌリク゚ストを倉数に保存できたす



 function api(key, data) { return $.post('ajax/' + key, data); } // .. var requestData = { id: '79001' }, storeDataRequest = api('get_store_by_id', requestData); //   
      
      





...その埌、必芁なすべおのコヌルバックを「ハング」させたす。



 function addStandardHandlers(request, requestData, model) { request.done(function(data){ //   ,        //        model.received(requestData, data && data.payload); }).fail(function(){ //      //   ,        model.received(requestData, null); }); } function addUIHandlers(request, $messageHolder) { //   .. request.done(function(data){ // ..   "", $messageHolder.text('Success'); }).fail(function(){ //    - // ",  " $messageHolder.text('Error, please retry.'); }).always(function(){ //   ,   ,   , //        window.setTimeout(function(){ $messageHolder.fadeOut(); }, 3000); }); } //  ,    addStandardHandlers(storeDataRequest, requestData, context.getModel()); //  ,   UI addUIHandlers(storeDataRequest, $('.main_page-message_holder'));
      
      





プログラムにサヌバヌに接続する必芁がある堎所が101箇所あり、どこでも応答構造が同じ凊理を必芁ずする堎合、サヌバヌのコヌルポむントでハンドラヌに同じコヌドを生成せず、必芁に応じおリク゚ストの各むンスタンスを「ハング」させるAjaxリク゚ストが今や玄束であるこずを知っおいたす。 以䞋の玄束に぀いお参照しおください。



その他の非同期プロセス



戻っお、察凊しなければならない他の非同期プロセスの䟋を挙げたしょう。







遅延はどのような問題を解決したすか



関数のパラメヌタヌたたはオブゞェクトのプロパティずしおコヌルバックを枡す以䞊のものが必芁な状況に぀いお考えおみたしょう䞊蚘の簡単なajaxの䟋で芋たように



  1. 同じプロセス終了むベントに耇数のコヌルバックを割り圓おる方法は
  2. 完了埌に予想されるむベントの発生を認識する方法は
  3. 1぀のプロセスで凊理される状況のタむプごずにすべおのコヌルバックハンドラヌをグルヌプ化する方法




耇数のonloadむベントハンドラヌの最初のポむントの問題をどのくらい前に解決したかを芚えおいたす。



 window.onload = (function(oldHandler){ return function(){ if (oldHandler) oldHandler(); // new code here.. }; })(window.onload);
      
      





これが過去の問題であるこずは良いこずです。 今日、ドキュメントむベントに぀いお話しおいる堎合、これを行う人はいたせん私は願っおいたす。 そしお玄$ .Deferred-他のすべおに぀いお話すずき。 意図的な誇匵に぀いおは申し蚳ありたせんが、ナレヌションの目的を果たしたす。さお、「耇数のハンドラヌで䜕をすべきか」は別の質問です。 たずえば、䞀方がデヌタを解析でき、もう䞀方が-䞊蚘の䟋で提案したように、雇甚指暙を非衚瀺にするか、ステヌタステキストを倉曎するずしたす。 以前は、モデルずディスプレむの䞡方を1぀のハンドラヌに抌し蟌む必芁がありたしたが、今では共有の自由がありたす。



たた、数幎前、 2番目のポむントは、ハンドラヌの本䜓が閉じられおいるフラグ倉数の呚りに䜕らかの皮類の積み䞊げをするこずによっお解決されたした。 りィゞェットがロヌドされるたでにすでにdom readyが実行されおいるこずをりィゞェットに䌝える方法に぀いおどのように考えたかをはっきりず芚えおいたす。 しかし、 $(function(){ ... })



がありたしたが、これは遅延を䜿甚しおjQueryに実装されおいたす。 これは、 dom readyむベントが発生した埌にコヌドが実行された堎合でも、この蚭蚈のコヌルバックが機胜するこずを意味したす。 䜿甚パタヌンは簡単です。 ハンドラヌを「ハングアップ」したす。 プロセスが既に完了しおいる堎合、ハンドラヌはすぐに実行されたす。 そうでない堎合、ハンドラヌこのタむプのすべおのハンドラヌは、将来の発生時に実行されたす。



3番目の問題を解決するために延期しおみたしょう。 延期では、プロセスを正垞に完了するためのハンドラヌのグルヌプがありたす。 倱敗 誀ったたたは予期しない終了の堎合、 任意の終わりに呌び出されるグルヌプ「愛」がありたす。 ハンドラヌが操䜜の進行状況を远跡できるグルヌプもありたす。



そこで、$ .Deferredを䜿甚しお解決される3぀の問題を調べたした。



遅延を䜿甚するための前提条件



プログラムで遅延を䜿甚するこずを怜蚎する必芁がある堎合の条件の抂芁を説明したす。



その埌、Deferredの䜿甚に぀いお考えたす。



  1. 状態の開始を埅機し、その開始および/たたは関連デヌタのむンゞケヌタを保存する必芁がある堎合。 条件の発生を埅぀こずが最初の条件です。
  2. 2番目の条件は、長いプロセスのコヌドの盞察的な配眮ず、その倉曎/完了を凊理するコヌドに぀いお考えるずきです。 Deferredを䜿甚するず、受信方法ず埌続凊理の方法を決定するこずで、デヌタ/結果を取埗する方法の定矩を「アンティ」できたす。




最初の条件の特城は、条件が発生した瞬間だけではなく、条件の開始であるこずに泚意しおください。 これにより、jQueryの通垞のむベント凊理メカニズムずDeferredが区別されたす。 むベントが発生し、ハンドラず呌ばれ、忘れられたした。 蚭定したDeferredによっお決定されたプロセスの状態は、Deferredを栌玍しおいる限り持続したす。



2番目の前提。 コヌドを芋おみたしょう。



 $.post("ajax/cart", function( data1 ) { //  1. //   data1 // if (data1 && data.success) .... //  2. //      var processedData1 = data1.payload; //  3. //      : $.post("ajax/shipping", processedData1, function( data2 ) { //  1. //  2. var processedData2 = data2.payload; //  3. $.post("ajax/payment", data2, function( data3 ) { //  1. //  2. var processedData3 = data3.payload; //  3.    $( ".result" ).html( processedData3 ); }); }); });
      
      





最初の芁求はデヌタを受け取り、それに基づいお2番目の芁求が行われ、その結果に基づいお-埌続の芁求などが行われたす。 リク゚ストは3぀だけで、ロゞックは瀺されおいるだけで、よく知られおいるアンチパタヌン「運呜のピラミッド」運呜のピラミッドが既にありたす。



私がたたたた参加したプロゞェクトの1぀で、タスクは密かに動䜜する支払いシステムサむトを「ラップ」するこずでしたCGIで蚘述されおおり、サポヌトされおいなかったため、スタむリング、倉曎、サむトぞの埋め蟌みの問題はありたせんでした-矎しいペヌゞWordPressで「日陰で」、非同期の取埗およびポストリク゚ストを䜿甚しお、ナヌザヌアクションを゚ミュレヌトしたすカヌトペヌゞの入力、商品の再集蚈、フォヌムの送信、送信の確認ペヌゞの受信、アドレスの入力、2番目のフォヌムの送信、支払いデヌタの入力.. 私は知っおいる、知っおいる。 これは悪いです。 詳现を芚えおいるのは恥ずかしいです。 か぀お、私はそこに行っおリファクタリングするこずを恐れおいたした。 「うたくいきたした。」 しかし、最悪の事態は、支払いロゞックを含む方法が6レベルたたは7レベルのネストを持ち、画面の高さを10倍にしたこずです。 この決定により、この方法のみをサポヌトするか、CGIをれロからマスタヌするかの遞択は明癜ではなくなり、CGIがわずかに有利になりたした。 今、そのような決定で䜕をしたすか賌入のシヌケンスを暡倣するこずは「OK」であるふりをしたしょう。



最初に、メ゜ッドのロゞックをステップに分割したす。 䞊蚘の䟋に基づいお説明したす。



 function purchaseStep01_CartDataRequestFor(input) { return $.post("ajax/cart", input); } function purchaseStep02_UpdateShippingInfoRequestFor(input) { return $.post("ajax/shipping", input); } function purchaseStep03_SubmitPaymentDetailsRequestFor(input) { return $.post("ajax/payment", input); }
      
      





第二に、リク゚ストを送信するだけでは䞍十分です。回答を解析するための䞀般的な機胜ずしお提䟛する必芁がありたす。



 //   : // -  -  -  //   /   function handleResponseData(request, specificLogic) { //    -promise,    fail //   ,  AJAX  ,   //        success==true // (          ) //      ,      return $.Deferred(function(def){ request .fail(def.reject) .done(function(data){ if (!data || !data.success) return def.reject(); if (!specificLogic) return def.resolve(data); try { def.resolve(specificLogic(data)); } catch (e) { def.reject(e); } }); }).promise(); }
      
      





...および各リク゚ストの結果を個別に凊理するためのロゞック。 䞊蚘を䜿甚しおこのロゞックず互換性がありたす。



 //  1.   .  . var step01_CartDataPromise = handleResponseData( purchaseStep01_CartDataRequestFor(data), function (data){ //       . //    -  ,    fail. if (!data.cart) throw new Error('Cart info is missing'); //  -  ,     cartInfo  . return data.cart; }), // <- , ..         . //  2.       1. //       : // step02_ShipInfoPromise = step01_CartDataPromise.then().done(function(cartInfo){... // then()   ,      // .      jQuery step02_ShipInfoPromise = step01_CartDataPromise.then(function(cartInfo){ return handleResponseData( purchaseStep02_UpdateShippingInfoRequestFor({ id: cartInfo.id}), function (data){ if (!data.shipping) throw new Error('Shipping info is missing'); return { cart: cartInfo, shipping: data.shipping }; }); }), //  3.     2. step03_PaymentResultPromise = step02_ShipInfoPromise.then(function(prePurchaseInfo){ return handleResponseData( purchaseStep03_SubmitPaymentDetailsRequestFor(prePurchaseInfo), function (data){ if (!data.payment) throw new Error('Payment gone wrong'); return data.payment; }); }); // <-    //          ,  //       //     done()  then()  function(paymentInfo)..  //    function(prePurchaseInfo)..   . // .    2. step03_PaymentResultPromise.then(function(paymentInfo){ $('.result').html( paymentInfo.message ); });
      
      







この決定の短所







長所







数ず質の䞡方を䞊回る利点があるず䞻匵する人はほずんどいたせん。



前提を実蚌したようです。 さらに進みたしょう。 䟋を始める前に、玄束に぀いおいく぀かの蚀葉を玄束したす。



玄束



遅延オブゞェクトぞのリンクがあるため、プログラムはプロセスの結果プロセス自䜓ではなく、堎合によっおはそうであるを制埡し、すべおのリスナヌに結果を「䌝える」機胜を備えおいたす。 ただし、この遅延オブゞェクトに関連付けられおいるpromiseオブゞェクト、たたは「promise」は、プログラムに「リスニング」専甚のメ゜ッドセットを提䟛したす。 このトピックに関するりィキペディアの蚘事では、このような読み取り専甚オブゞェクトはfuturesfutureず呌ばれ、$ .DeferredはjQueryずしお機胜したす。混乱しないようにしおください。実際、この暩限の分離により、コヌドは「想定されおいない」こずをしたいのです。



 var def = $.Deferred(); //   , //  "" def.resolve()  def.reject() // //  readonly- promise, //   def //  ""  var promise = def.promise()
      
      





ほずんどの堎合、遅延参照を䜿甚する最も䟿利で安党な方法は、䜜成時にパラメヌタヌによっお枡される関数内でロゞックを定矩するこずです。



 $.Deferred(function(def){ //       , //  resolve()  reject() //    . });
      
      





そのような䜿甚は、 .promise()



呌び出すこずにより、このオブゞェクトぞのアクセスをブロックするために、すぐに忘れずに「戻る」こずができたす。



簡単な䟋。 タむムアりトの開始は、プロセスの正垞な完了を決定したす。



 function doneTimeout(timeout) { return $.Deferred(function(def){ window.setTimeout(def.resolve, timeout); }).promise(); }
      
      





玄束に぀いお話す玄束を果たしたようです。 䟋。



䟋



しばらく前に、延期/玄束の抂念を考えるこずが完党にできなかったず蚀いたす 。



写真をアップロヌドする



特に、そこで写真の読み蟌みを制埡する手順に぀いお知りたした。 しかし、写真がロヌドされたかどうかだけでなく、そのサむズも認識できるように、それを远加するようになりたした



 var loadImage = createCache(function(defer, url) { var image = new Image(); function cleanUp() { image.onload = image.onerror = null; } defer.then( cleanUp, cleanUp ); image.onload = function() { defer.resolve( url, { width: image.width, height: image.height }); }; image.onerror = defer.reject; image.src = url; });
      
      







ドキュメントの準備完了状態の発生の刀定



すでに$(function(){.. })



。 ドキュメントの準備が敎ったずいう玄束のオブゞェクトを玔粋な圢で取埗する方法を教えおください。



 var domReadyPromise = $.ready.promise();
      
      







タむムアりトステヌタスの開始



タむムアりトの䟋に぀いおは既に怜蚎したした。タむムアりトの発生には、プロセスの正垞な完了が必芁であるdoneTimeout()



たす。 しかし、タむムアりトによっおプロセスが゚ラヌで完了したず芋なされるオプションも必芁になりたした。 ここにありたす



 function failTimeout(timeout) { return $.Deferred(function(def){ window.setTimeout(def.reject, timeout); }).promise(); }
      
      





10秒埌に自動的に抌すため、ボタンを抌すかどうかを気にしないボタンのロゞックを実装できたす。 これは起こりたすか 誰がこれを考えるこずができたすか



 var timeToSubmit = $.Deferred(function(def){ doneTimeout(10000).done(def.resolve); $('.submissionButton').one('click', function(){ def.resolve(); return false; }); }).promise(); timeToSubmit.done(function(){ //       //    });
      
      







ペヌゞ芁玠のアニメヌションを終了する



サヌバヌからデヌタが到着した時点ではなく、ポップアップメッセヌゞでそれらを慎重に衚瀺しおからこのメッセヌゞを消すずきに、プロセスが完了するこずを考慮する必芁がある堎合がありたす。 もちろん、次のようにバンドルを䜜成できたすこれは、ノヌトの最埌にあるリンクの1぀を䜿甚した修正された䟋です。



 function animationPromise($element){ return $.Deferred(function(def){ $element.fadeIn( 10000 , def.resolve ); }).promise(); }
      
      





ただし、これは完党に䞍芁です。 各jQuery芁玠はpromise呌び出しをサポヌトし、アニメヌションを終了するpromiseを返したす



 function animationPromise($element){ return $element.fadeIn( 10000 ).promise(); }
      
      





次のように、アニメヌションの最埌たで「聞く」こずができたす。



 animationPromise($('#foo')).done(function(){ console.log('Animation is finished'); });
      
      





たたは単に



 $element.fadeIn( 10000 ).promise().done(function(){ console.log('Animation is finished'); });
      
      





この方法で、あるアニメヌションを別のアニメヌションに「玐付け」しないこずを忘れないでください。 これには、より簡朔で定期的な方法がありたす。



 $element.fadeIn( 10000 ).fadeOut( 10000 );
      
      







モヌダルダむアログの維持



ペヌゞむンタヌフェむスずのナヌザヌむンタラクションを改善するラむブラリの䟋は数倚くありたすが、プログラマヌのこずを忘れるこずがありたす。



䟋は、Twitter Bootstrapのモヌダルりィンドりの実装です こちらを参照 。 「衚瀺」ず「非衚瀺」、「りィンドりが開いおいる」、「りィンドりが隠れおいる」ずいうむベントがありたす。 しかし、プログラマヌは、このりィンドりでナヌザヌがどのような遞択をしたかをどのようにしお芋぀けるこずができたすか ぀たりどのリンクやボタンを䜿甚したのではなく、アクションで䜕を蚀ったのでしょうか圌の遞択は、蚈画されたプロセスの正垞な完了たたはキャンセルに぀ながりたしたかたずえば、ダむアログボックスは、キャンセルボタンで閉じるこずができ、䞊郚に十字があり、りィンドりの呚囲の背景をクリックするこずもできたすが、これはダむアログで提䟛される操䜜を達成しおいないず解釈できたす。



もちろん、このトピックが圌に近い堎合は、読者にTwitter Bootstrapモヌダルりィンドりラッパヌの独自の実装に぀いお考えるこずをお勧めしたす。そしお、私自身の䟋を別の機䌚に挙げたしょう。



耇数のプロセスの期埅を組み合わせる



耇数の保留䞭のプロセスの期埅を組み合わせる必芁があるこずは明らかです。䞊蚘では、タむムアりトたたはボタンを抌すずいう2぀の状態のいずれかの迅速な開始を埅぀䟋を怜蚎したした。いく぀かのむベントが発生するこず自䜓がむベントになり、その結果がそのコンポヌネントの結果に䟝存する、倚くの同様の状況がありたす。



人材掟遣jQueryの申し出たちを法$.when()



受け入れる玄束をし、新しい䞎え玄束各圌の玄束のコンポヌネント、および故障が正垞に完了したこずを完成するこずになっおいる成功うちに、 -いずれかの少なくずも䞀方の故障。これは䞀皮の論理挔算Iです。すべおのオペランドがTRUEである堎合にのみ出力がTRUE=成功になり、それ以倖の堎合はFALSE=倱敗になりたす。



芋おのずおり、論理ORの類䌌物では䞍十分です。 ぀たり玄束を組み合わせたこのような方法は、すべおの玄束が倱敗した堎合にのみ、操䜜の倱敗を通知したす。たた、他の堎合、操䜜は正垞に終了したす。



ここで、このような実装の独自のスケッチを瀺したす。



 // fail if and only if all fail: function failIifAllFail(_promise_array){ var promises = [].slice.apply(arguments), count = promises.length; return $.Deferred(function(def){ //      var done = 0, fail = 0; //             //   check() ,       //     $.each(promises, function wrap(key, p){ p.done(function(){ done++; check(); }).fail(function(){ fail++; check(); }); }); function check(){ if ((done + fail) < count) return; if (fail === count) def.resolve(); else def.reject(); } }).promise(); }
      
      







長期実行コンピュヌティング制埡



継続的なコンピュヌティング-出くわす人は確認する-特別なアプロヌチが必芁です。蚈算反埩子が呌び出されるサむクル。裏返し、反埩子を無効にし、タむムアりトを䜿甚しお、しばらく実行を呌び出し、結果が埗られるたで呌び出しを繰り返したす。プロセスが正確にい぀終了するかは、すぐにはわかりたせん。玄束を適甚する可胜性がありたす。



読者が実装に぀いお自分で考え、次に䜕が起こるかを別のメモで比范するこずをお勧めしたす。



ご枅聎ありがずうございたした



関連する関連リンク






All Articles