jQuery.Deferredオブジェクトのソースコード解析

11759 ワード

Deferredって何?
実際にdeferredはPromise/A+標準のjQueryの実装であり,非同期操作を管理するオブジェクトである.
jQuery.Deferred大体ロジック
  • ソースコードの概略コードは次の
  • です.
    jQuery.extend(
      Deferred: function( func ) {
        //               、      、      、    
        var tuples = [
          [ ... ], //    fulfilled
          [ ... ], //    rejected
          [ ... ]  //    progress 
        ], 
          //       ,       
          state = "pending", 
          
          // promise   ,        ,  done()、fail()、progress()、then()  ,  deferred      
          promise = { ... },
          
          // deferred   ,          ,         ,     promise   
          deferred = {}; 
        
        //   deferred   promise     done,fail,progress        
        //   deferred     resolve、reject、notify         
        jQuery.each( tuples, function( tuple, i ) {
          ...
        } );
        
        //   promise         deferred  
        promise.promise( deferred );
    
        // Deferred()          ,func       deferred,         
        if ( func ) {
          func.call( deferred, deferred )
        }
    
        //      deferred   
        return deferred;
      }
    )
    

    まとめ:
  • はtuples、state、promise、deferred変数を宣言する.
  • tuplesをdeferredに混入する.
  • promiseをdeferredに混入する.
  • deferredの構成関数funcがあれば実行する.
  • は最後にdeferredオブジェクトを返す.

  • 配列を構成するtuplesとして
  • tuplesの具体的な定義
  • を見てみましょう.
    // tuples       ,    deferred      ,   tuple      
    // tuple[ 0 ]         
    // tuple[ 1 ]         
    // tuple[ 2 ]     tuple[ 1 ]             ,   resolve   reject       
    // tuple[ 3 ]     then()             
    // tuple[ 4 ]     then()       ,              
    // tuple[ 5 ]      
    var tuples = 
      [ 
        [ "notify", "progress", jQuery.Callbacks( "memory" ),
          jQuery.Callbacks( "memory" ), 2 ],
    
        [ "resolve", "done", jQuery.Callbacks( "once memory" ), 
          jQuery.Callbacks( "once memory" ), 0, "resolved" ],
    
        [ "reject", "fail", jQuery.Callbacks( "once memory" ),
          jQuery.Callbacks( "once memory" ), 1, "rejected" ]
      ]
    

    まとめ:この配列は実際にdeferred全体の基本メソッド名、ステータス名、コールバック管理コンテナを定義します.
    promiseオブジェクト定義
  • promiseオブジェクトはdeferredの非常に重要なオブジェクトであり、deferredのサブセットであり、一連のコールバック登録関数を定義し、宣言時に次のメンバーオブジェクトがある:
  • promise = {
      
      //       
      state: function() {
        return state;
      },
    
      //    onFulFilled   onRejected          
      always: function() {
        deferred.done( arguments ).fail( arguments );
        return this;
      },
    
      // catch    ,       then    onRejected   
      "catch": function() {
        return promise.then( null, fn );
      },
      pipe: function() { ... },
      then: function() { ... },
      promise: function() { ... }
    }
    
  • 以下でpipeの実装pipeを詳細に説明すると、フック関数として理解することができ、3つのコールバックとして参照され、状態戻り結果を前処理する.ソースコードは次のとおりです:
  • pipe: function( /* fnDone, fnFail, fnProgress */ ) {
    
      var fns = arguments;
    
      //          deferred   ,           
      //     deferred             
      return jQuery.Deffered( function( newDefer ) {
    
        jQuery.each( tuples, function( i, tuple ) {
          
          //    tuple[ 4 ]                fn
          var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
          
          //                
          deferred[ tuple[ 1 ] ]( function() {
            
            //        fn    returned
            var returned = fn && fn.call( this, arguments );
    
            // returned       promise    deferred,   promise   
            //      ,      deferred            returned      
            //         Promise/A+   
            if ( returned && isFunction( returned.promise ) ) {
              
              //        deferred            returned       
              returned.promise()
                .progress( newDefer.notify )
                .done( newDefer.resolve )
                .fail( newDefer.reject )
            } else {
              
              //   ,returned               
              //   ,   this      deferred               
              //      fn,      deferred            
              newDefer[ tuple[ 0 ] + 'With' ](
                this,
                fn ? [ returned ] : arguments
              );
            }
          } );
        } );
    
        fns = null;
      } ).promise();
    }
    

    まとめ:
  • pipe関数は、入力されたコールバックで元のdeferredを装飾し、新しいdeferredオブジェクトを返します.
  • コールバックの戻り値もpromiseやdeferredのような非同期オブジェクトである場合、新しいdeferredの状態はこの戻り値の状態に依存する.
  • 戻り値が非同期オブジェクトである場合、新しいdeferredの状態遷移関数のパラメータとして直接使用される.
  • 新しいdeferredの状態遷移関数のコンテキストは、古いdeferredの状態遷移関数のコンテキストを継承する.
  • コールバックが入力されていない場合、新しいdeferred解析の結果、古いdeferredの状態遷移関数が参照される.
  • 以下thenメソッドの実装thenメソッドはpromise標準のコアメソッドであり、最も複雑な1つでもあり、まずコード:
  • then: function( onFulfilled, onReject, onProgress ) {
    
      //               
      var maxDepth = 0;
      
      //         ,           
      function resolve( depth, deferred, handler, special ) {
        //       ,           
        return function() {
          ...
        }
      }
      
      //        deferred   ,              
      return jQuery.Deferred( function( newDefer ) {
        
         //     ,  then               ,           
         //      ,then          tuple[ 3 ]  
         //     done、fail、progress          tuple[ 2 ]  
         tuples[ 0 ][ 3 ].add(
            resolve(
              0,
              newDefer,
              isFunction( onProgress ) ?
                onProgress :
                Indentity
            )
         );
    
         tuples[ 1 ][ 3 ].add(
            resolve(
              0,
              newDefer,
              isFunction( onFulfilled ) ?
                onFulfilled :
                Indentity
            )
         );
    
         tuples[ 2 ][ 3 ].add(
            resolve(
              0,
              newDefer,
              isFunction( onRejected ) ?
                onRejected :
                Thrower
            )
         );
    
      } ).promise()
    }
    

    次にresolve解析法の実装を具体的に見てみましょう.
    function reslove( depth, deferred, handler, specail ) {
      return function() {
        
        //      ,          ,    resolveWith      
        //         that,    mightThrow       
        var that = this,
          args = arguments,
          
          mightThrow = function() {
            var returned, then;
            
            //    depth < maxDepth,    depth       deferred        ,       
            if ( depth < maxDepth ) {
              return;
            }
            
            returned = handler.apply( that, args );
            
            //        deferred     promise,      
            //      deferred                  ,            
            if ( returned === deferred.promise() ) {
              throw new TypeError( "Thenable self-resolution" );
            }
            
            //    returned       promise   ,   returned.then    then    
            then = returned &&
              ( typeof returned === "object" ||
                typeof returned === "function" ) &&
              returned.then;
            
            //    then   function,     returned      promise   
            if ( isFunction( then ) ) {
    
              //     special,        onProgress   ,     resolve   
              //     function      progress      ,    maxDepth,     notify   
              if ( special ) {
                then.call(
                  returned,
                  resolve( maxDepth, deferred, Indentity, special ),
                  resolve( maxDepth, deferred, Thrower, special )   
                );
              } else {
                
                //   ,  function       done    ,        special     deferred.notifyWith
                maxDepth++;
                then.call(
                  returned,
                  resolve( maxDepth, deferred, Indentity, special ),
                  resolve( maxDepth, deferred, Thrower, special ),
                  resolve( maxDepth, deferred, Indentity, deferred.notifyWith )
                );
              }
            } else {
              
              //    then    function,    returned        value  
              //    handler    Indentity,  handler          
              //              0,    deferred                       deferred         
              if ( handler !== Indentity ) {
                that = undefined;
                args = [ returned ];
              }
              
              //    special   ,     notifyWith   ,      
              //    handler         ,      fulfilled,   deferred.resolveWith     
              ( special || deferred.resolveWith )( that, args );
    
            }
          },
    
          // special      notify   ,      ,     mightThrow   
          //       resolved   rejected,      
          process = special ? 
            mightThrow :
            function() {
              try{
                mightThrow()
              } catch( e ) {
                if ( jQuery.Deferred.exceptionHook ) {
    
                  //            
                  jQuery.Deferred.exceptionHook( e, process.stackTrace )
                }
    
                //                     
                if ( depth + 1 >= maxDepth ) {
                  if ( handler !== Thrower ) {
                    that = undefined;
                    args = [ e ];
                  }
    
                  //               ,          reject    
                  //         >= maxDepth
                  deferred.rejectWith( that, args );
                }
              }
            };
    
        //    depth    0,      process
        if ( depth ) {
          process();
        } else {
          if ( jQuery.Deferred.getStackHook ) {
            process.strackTrace = jQuery.Deferred.getStackHook();
          }
    
          //         
          window.setTimeout( process );
        }
      };
    }
    

    resolveの深さについては、いくつかの例を挙げます.
    let defer = $.Deferred();
    let defer2 = $.Deferred();
    
    let newDefer = defer.then(
      function() {
        console.log( 'defer resolved' )
        return defer2;
      },
      function() {
        console.log( 'defer rejected' )
      },
      function() {
        console.log( 'defer notified ')
      }
    )
    
    // defer resolve  ,          
    //     0       defer     
    //       1          
    //        :defer is resolved
    defer.resolve();
    
    //              0,    maxDepth = 1,          
    //              
    //   :defer      onProgress        defer.resolve()        
    //   newDefer   onProgress         depth      
    defer.notify();
    
    //     newDefer           defer2,      newDefer   onProgress     
    //   :newDefer is notified
    defer2.notify();
    
    let defer3 = $.Deferred();
    
    //   ,defer2        defer3,        ,   maxDepth = 2
    // newDefer      defer3      
    defer2.resolve( defer3 );
    
    //    defer2      
    //    depth = 1 < maxDepth,
    //           
    defer2.notify()
    
    // defer3     ,   newDefer      fulfilled
    //   :newDefer is resolved
    defer3.resolve()
    

    まとめ:
  • then注入コールバックは深層の非同期ネストをサポートするが、done、failはできず、所属する非同期オブジェクトの状態解析が完了した後に実行される.
  • thenは非同期のタイミング関係を決定するだけで、各非同期オブジェクトの最終状態が達成されるかどうかを保証するものではないことを意味的に感じることができる.

  • ここではjQuery.eachはtuplesをpromiseとdeferredに注入する
    ソースとコメントを直接見ればいいです.
    jQuery.each( tuples, function( i, tuple ) {
    
      // list    done、fail、progress          
      var list = tuple[ 2 ],
        //      
        stateString = tuple[ 5 ];
      
      //             done、fail、progress
      promise[ tuple[ 1 ] ] = list.add;
      
      //          
      if ( stateString ) {
        list.add(
          
          //      
          function() {
            state = stateString;
          },
          
          //             (done、fail、progress)       
          tuples[ 3 - i ][ 2 ].disable,
    
          //         then        
          tuples[ 3 - i ][ 3 ].disable,
          
          //    progress      
          tuples[ 0 ][ 2 ].lock,
          tuples[ 0 ][ 3 ].lock
        );
      }
    
      //   then        fire       [ done, fail, progress ]       
      //    then          onDone、onFail、onProgress     
      list.add( tuple[ 3 ].fire );
      
      //   deferred          ,    this      
      deferred[ tuple[ 0 ] ] = function() {
        deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
        return this;
      };
    
      //   deferred               
      deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
    
    } );
    

    最后に:私もちょうどjQueryのソースコードを学ぶことを始めて、この文はただノートと心得と分かち合うことを学ぶだけとして、Deferredについて、まだ多くの理解しない地方があって、各位の大きい人の指导を歓迎します