モバイルWeb app開発必須-非同期キューDeferred

13265 ワード

背景
モバイルWeb appの開発では、非同期コードはよくあることです.例えば、一般的な非同期操作があります.
  • Ajax(XMLHttpRequest)
  • Image Tag,Script Tag,iframe(原理類似)
  • setTimeout/setInterval
  • CSS3 Transition/Animation
  • HTML5 Web Database
  • postMessage
  • Web Workers
  • Web Sockets
  • and more…

  • 後のいくつかはCSS 3 HML 5が加入した新しいAPIである.これらのインタフェースは非同期の操作を生成します
    例えば、本人のphonegapプロジェクトでは、HTML 5のローカルデータベース(HTML 5 Webデータベース)を操作するのは非同期のプロセスであり、複数のクエリーを同時に実行する場合、同期コードはデータクエリーが終了した後に呼び出されるのを待つ必要があります.
    添付ソース:複数の非同期クエリーを実行する
       /**
         *      
         * @return 
         */
        proto.initProcess = function(){
            var self = this,
                prev = null ,
                curr = null ,
                next = null ;
            debug.group("start of init process");
            var idx = self.chapterIndex;
            debug.info("PageBase:           !");
            self.initProcessBefore();
    
            if(idx == 0){
                debug.info("PageBase:               ");
                debug.info("PageBase:           !");
                curr = self.process(self.chapters[idx]);
                curr.then(function(pages){
                    debug.info(self.format("PageBase:          ,      {0}" , pages.length));
                    self.cPages = pages;
                    if(self.isChangeFont){
                      self.idx = Math.ceil((pages.length - 1) * self.idx);                  
                    }
    
                    self.cPages.idx = idx;
    
                    /////////////////////////////////////////////////
                    //
                    // 2013.1.10  
                    //               
                    //
                    if(1 === self.chapters.length){
                      deferred.all([curr]).then(self.steup.bind(self));   
                    }else{
                      debug.info("PageBase:          !");
                      next = self.loadNextData(idx + 1);
                      next.then(function(args){
                          debug.info(self.format("PageBase:          ,      {0}" , args.pages.length));
                          self.nPages = args.pages;
                          self.nPages.idx = idx + args.index;
                          debug.info(self.format("PageBase:          ,     {0}     {1}     {2}      {3}" 
                                  , self.cPages.idx , self.cPages.length , self.nPages.idx , self.nPages.length));
                    
                          debug.info("PageBase:          ,        !");
                      });
                      deferred.all([curr , next]).then(self.steup.bind(self));   
                    }
    
                });
            }else if(idx == self.chapters.length -1){
                debug.info("PageBase:                ");
                debug.info("PageBase:           !");
                prev = self.loadPrevData(idx - 1);
                prev.then(function(args){
                    self.pPages = args.pages;
                    self.pPages.idx = args.index + 1;
                    debug.info(self.format("PageBase:               ,      {0}" , args.pages.length));
                    curr = self.process(self.chapters[idx]);
                    curr.then(function(pages , data){
                        if(self.isChangeFont){
                          self.idx = Math.ceil((pages.length - 1) * self.idx);                  
                        }
                        self.cPages = pages ;
                        self.cPages.idx = idx;
                        debug.info(self.format("PageBase:           ,      {0}" , pages.length));
                        debug.info(self.format("PageBase:          ,     {0}     {1}     {2}     {3} " 
                                , self.pPages.idx , self.pPages.length , self.cPages.idx , self.cPages.length ));
                  
                        debug.info("PageBase:          ,        !");
                    });
                    deferred.all([prev , curr]).then(self.steup.bind(self));
                });
            }else{
                debug.info("PageBase:               ");
                prev = self.loadPrevData(idx - 1);
                debug.info("PageBase:              !");
                prev.then(function(args){
                    self.pPages = args.pages ;
                    self.pPages.idx = args.index;
                    debug.info(self.format("PageBase:             ,      {0}" , args.pages.length));
                    debug.info("PageBase:          !");
                    curr = self.process(self.chapters[idx]);
                    curr.then(function(pages , data){
                        if(self.isChangeFont){
                            self.idx = Math.ceil((pages.length) * self.idx); 
                            // console.log("spages.length - 1",pages.length)      
                            // console.log("self.idx",self.idx)             
                        }
                        self.cPages = pages ;
                        self.cPages.idx = idx;
                        debug.info(self.format("PageBase:          ,      {0}" ,pages.length));
                        debug.info("PageBase:              !");
                        next = self.loadNextData(idx + 1);
                        next.then(function(args){
                            self.nPages = args.pages ;
                            self.nPages.idx = idx + args.index;
                            debug.info(self.format("PageBase:             ,      {0}" , args.pages.length));
                            debug.info(self.format("PageBase:          ,     {0}     {1}     {2}     {3}     {4}      {5}" 
                                , self.pPages.idx , self.pPages.length , self.cPages.idx , self.cPages.length , self.nPages.idx , self.nPages.length));
                            debug.info("PageBase:          ,        !")
                        });
                        deferred.all([prev , curr , next]).then(self.steup.bind(self));  
                    });
                });
           }
    

     
    コードの整理方法
    しかし、非同期+コールバックのモードでは、一連の非同期操作をフロー制御する必要がある場合、必ずコールバックネストに直面するようです.そのため、どのように非同期操作を「平らにする」のか、より良い方法で非同期プログラミングの体験を最適化し、同時により丈夫な非同期コードを書くのかは、この2年間、フロントエンドの輪の中で人気のある話題だ.
    代表的
  • メッセージドライバ-@朴霊を表すEventProxy
  • .
  • Promiseモード-代表:CommonJS Promises2jQuery2Dojo
  • 二次コンパイル-代表:@趙さんJscex
  • jQueryはこのwhenメソッドを実装した唯一のライブラリである.他のpromisesライブラリ、例えばQDojo、およびwhenは、Promises/B specに従ってwhenメソッドを実装したが、注釈者が言及したwhenメソッドは実装されなかった.しかし、Qライブラリにはallメソッド、whenがあります.jsにもparallelメソッドがあり、上のjQueryとwhenメソッドは、任意の数のパラメータではなく、配列タイプのパラメータを受け入れるだけで機能します.

  • Jquery Deferredのレビュー
  • は1.5バージョンから、jQueryにDeferred機能が追加され、イベント処理キューがより充実しています.このメカニズムでAjaxモジュールを書き換えました.まだAjaxの番になっていませんが、次のイベント処理関数ではこのメカニズム
  • に関連しています.
  • Deferredはコールバック関数を1つのキューに登録し、統一的に管理し、これらの関数を同期または非同期で呼び出すことができる.jQuery.Deferred()は、Deferredオブジェクトを作成するために使用されます.オブジェクトには状態値があり、Rejected、Resolved、初期状態の3種類があります.ここでResolvedは操作が正常に完了したことを示し、Rejectedはエラーが発生し、呼び出しに失敗したことを示します.Deferredオブジェクトの主なメンバーは次のとおりです:
  • done(callback):resolved状態で呼び出されるcallback関数を登録します.
  • fail(callback):ステータスがrejectedの場合に呼び出されるcallback関数を登録します.
  • always(callback):resolvedまたはrejectedにかかわらず呼び出されるcallback関数を登録します.
  • then(successCallback,failureCallback):成功と失敗のコールバック関数を同時に入力します.
  • pipe(successFilter,failureFilter):正常および失敗したコールバック関数を呼び出す前に、pipeによって指定された関数を呼び出す.関数呼び出しをブロックするパイプメカニズムです.
  • resolve(args):ステータスをResolvedに設定します.
  • reject(args):ステータスをRejectedに設定します.
  • promse():resolveとrejectがない不完全なDeferredインタフェースを返します.つまり、Deferredオブジェクトの状態は変更できません.読み取り専用ビューと見なすことができます.これは、外部関数がコールバック関数を早期にトリガーしないようにするためです.例えば$ajaxは、バージョン1.5以降、XMLHttpRequestではなく、XMLHttpRequestとDeferredオブジェクトインタフェースがカプセル化されたobjectを返します.ここでDeferred部分はpromise()で得られ,外部関数にresolveとrejectを呼び出させずajaxが完了する前にコールバック関数をトリガすることを防止する.この2つの関数の呼び出し権限をajax内部に保持します.

  •   deferred-js
    本人はプロジェクトの中でPromise/A規範で実現したdeferred-jsを使って、比較的に簡単で軽量である.
    使用方法
    API:
      var DeferredAPI = {
          deferred     : deferred,
          all          : all,
          Deferred     : Deferred,
          DeferredList : DeferredList,
          wrapResult   : wrapResult,
          wrapFailure  : wrapFailure,
          Failure      : Failure
      }

    最も簡単によく使われるケース
               //Deferred    
               var d = new deferred.Deferred()
    
               //             
               d.then(function(result) {
                   console.log('Hello ' + result)
                   return result
               })
    
               //       
               d.resolve('World')

    各リンクは、1つのコールバックチェーンで2つの関数であり、1つの成功、1つの失敗を表すことができます.
    成功したコールバックは1つしかありません
    d.then(function(result) {
        //      
        return result
    })

     
    失敗コールバック
    d.fail(function(failure) {
        // optionally do something useful with failure.value()
        return failure
    });

     
    成功メソッドと失敗メソッドの追加
    d.then(function(result) {
        // do something useful with the result
        return result
    }, function(failure) {
        // optionally do something useful with failure.value()
        return failure
    })

    コールバックが成功しても失敗しても同じコードを実行します.
    d.both(function(result) {
        // in the case of failure, result is a Failure
        // do something in either case
        return result
    })

     
    提供されるケースなど、多くの非同期が操作されている場合、HTML 5データベースをN回実行した後、どのように操作しますか?
    次のケースの
     deferred.all([prev , curr , next]).then(self.steup.bind(self));  

    allのメソッドは、すべての遅延キューのロードが完了するのを待ってから、後続のコードを実行します.
     
    使いやすいし、シンプルで複雑な概念はありません
    チュートリアルの使用後、次のセクションのソースコードの実装