Deferred source code analysis of jQuery and jquerydeferred source code

Source: Internet
Author: User

Deferred source code analysis of jQuery and jquerydeferred source code

I. Preface

About the summer, we talked about ES6 Promise. In fact, jQuery had Promise before ES6, which is the Deferred object we know. The purpose is of course the same as ES6 Promise, avoid layer-by-layer nesting through chained calls, as shown below:

// Jquery version later than 1.8 function runAsync () {var def = $. deferred (); setTimeout (function () {console. log ('I am done'); def. resolve ('whatever') ;}, 1000); return def ;} runAsync (). then (function (msg) {console. log (msg); // => Print 'whateverer '}). done (function (msg) {console. log (msg); // => Print 'undefined '});

Note: Starting from jQuery1.8, the then method returns a new restricted deferred object, that is, deferred. promise ()-we will learn more about it in subsequent source code interpretations. Therefore, the above Code done prints 'undefined '.

Now, after reviewing the Deferred usage of jQuery, let's take a look at how jQuery implements Deferred. Of course, the version of jQuery is later than 1.8.

Ii. Deferred source code analysis of jQuery

The overall architecture is as follows:

jQuery.extend( {  Deferred: function( func ) {    var tuples = [        // action, add listener, listener list, final state        [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],        [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],        [ "notify", "progress", jQuery.Callbacks( "memory" ) ]      ],      state = "pending",      promise = {        state: function() {...},        always: function() {...},        then: function() {...},        promise: function( obj ) {...}      },      deferred = {};    // Keep pipe for back-compat    promise.pipe = promise.then;    // Add list-specific methods    jQuery.each( tuples, function( i, tuple ) {} );    // Make the deferred a promise    promise.promise( deferred );    // Call given func if any    if ( func ) {      func.call( deferred, deferred );    }    // All done!    return deferred;  }}

In terms of the overall architecture, if you understand the factory model in the design model, it is not difficult to see that jQuery. Deferred is a factory. Every time you execute jQuery. Deferred, a processed deferred object will be returned.

Next, we will further analyze the above Code.

First, it is the array tuples:

tuples = [  // action, add listener, listener list, final state  [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],  [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],  [ "notify", "progress", jQuery.Callbacks( "memory" ) ]]

At the beginning, tuples pre-defined three States for us-'refreshed', 'objected', and 'Pending', as well as a series of values and Operations corresponding to them, it is worth noting that each status calls a jQuery. the Callbacks method is as follows:

What is it?

jQuery.Callbacks = function( options ) {  // Convert options from String-formatted to Object-formatted if needed  // (we check in cache first)  options = typeof options === "string" ?    createOptions( options ) :    jQuery.extend( {}, options );  var // Flag to know if list is currently firing    firing,    // Last fire value for non-forgettable lists    memory,    // Flag to know if list was already fired    fired,    // Flag to prevent firing    locked,    // Actual callback list    list = [],    // Queue of execution data for repeatable lists    queue = [],    // Index of currently firing callback (modified by add/remove as needed)    firingIndex = -1,    // Fire callbacks    fire = function() {      // Enforce single-firing      locked = options.once;      // Execute callbacks for all pending executions,      // respecting firingIndex overrides and runtime changes      fired = firing = true;      for ( ; queue.length; firingIndex = -1 ) {        memory = queue.shift();        while ( ++firingIndex < list.length ) {          // Run callback and check for early termination          if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&            options.stopOnFalse ) {            // Jump to end and forget the data so .add doesn't re-fire            firingIndex = list.length;            memory = false;          }        }      }      // Forget the data if we're done with it      if ( !options.memory ) {        memory = false;      }      firing = false;      // Clean up if we're done firing for good      if ( locked ) {        // Keep an empty list if we have data for future add calls        if ( memory ) {          list = [];        // Otherwise, this object is spent        } else {          list = "";        }      }    },    // Actual Callbacks object    self = {      // Add a callback or a collection of callbacks to the list      add: function() {        if ( list ) {          // If we have memory from a past run, we should fire after adding          if ( memory && !firing ) {            firingIndex = list.length - 1;            queue.push( memory );          }          ( function add( args ) {            jQuery.each( args, function( _, arg ) {              if ( jQuery.isFunction( arg ) ) {                if ( !options.unique || !self.has( arg ) ) {                  list.push( arg );                }              } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {                // Inspect recursively                add( arg );              }            } );          } )( arguments );          if ( memory && !firing ) {            fire();          }        }        return this;      },      // Remove a callback from the list      remove: function() {        jQuery.each( arguments, function( _, arg ) {          var index;          while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {            list.splice( index, 1 );            // Handle firing indexes            if ( index <= firingIndex ) {              firingIndex--;            }          }        } );        return this;      },      // Check if a given callback is in the list.      // If no argument is given, return whether or not list has callbacks attached.      has: function( fn ) {        return fn ?          jQuery.inArray( fn, list ) > -1 :          list.length > 0;      },      // Remove all callbacks from the list      empty: function() {        if ( list ) {          list = [];        }        return this;      },      // Disable .fire and .add      // Abort any current/pending executions      // Clear all callbacks and values      disable: function() {        locked = queue = [];        list = memory = "";        return this;      },      disabled: function() {        return !list;      },      // Disable .fire      // Also disable .add unless we have memory (since it would have no effect)      // Abort any pending executions      lock: function() {        locked = true;        if ( !memory ) {          self.disable();        }        return this;      },      locked: function() {        return !!locked;      },      // Call all callbacks with the given context and arguments      fireWith: function( context, args ) {        if ( !locked ) {          args = args || [];          args = [ context, args.slice ? args.slice() : args ];          queue.push( args );          if ( !firing ) {            fire();          }        }        return this;      },      // Call all the callbacks with the given arguments      fire: function() {        self.fireWith( this, arguments );        return this;      },      // To know if the callbacks have already been called at least once      fired: function() {        return !!fired;      }    };  return self;};

 

I have carefully tasted the above jQuery. Callbacks source code. If you understand the publish subscriber mode in the design mode, it is not hard to find that it is a "Custom Event ".

Therefore, after jQuery. Callbacks is simplified, the core code is as follows:

JQuery. callbacks = function () {var list = [], self = {add: function () {/* add an element to list */}, remove: function () {/* remove the specified element from the list */}, fire: function () {/* traverse the list and trigger each element */}; return self ;}

Each time we execute the jQuery. Callbacks method, it returns an independent Custom Event object. When jQuery. Callbacks is executed in each status of tuples, it becomes very clear-an independent space is provided for each status to add, delete, and trigger events.

Well, we have roughly understood the variable tuples.

State is the state value of the deferred object. We can get it through the deferred. state method (we will see it later ).

Promise is an object with the state, always, then, and promise methods. Each method is described as follows:

Promise = {state: function () {// return status value return state;}, always: function () {// whether successful or failed, the deferred method will be executed. done (arguments ). fail (arguments); return this;}, then: function (/* fnDone, fnFail, fnProgress */){...}, // for the most important part, I will discuss promise: function (obj) {// extend promise later, as we will soon see promise. promise (deferred); return obj! = Null? JQuery. extend (obj, promise): promise ;}}

Then declared an empty object deferred.

Promise. pipe = promise. then. It's not cumbersome. Let's take a look at jQuery. each (tuples, function (I, tuple ){...}) The source code is as follows:

/*tuples = [  [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],  [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],  [ "notify", "progress", jQuery.Callbacks( "memory" ) ]]*/jQuery.each( tuples, function( i, tuple ) {  var list = tuple[ 2 ],    stateString = tuple[ 3 ];  // promise[ done | fail | progress ] = list.add  promise[ tuple[ 1 ] ] = list.add;  // Handle state  if ( stateString ) {    list.add( function() {      // state = [ resolved | rejected ]      state = stateString;    // [ reject_list | resolve_list ].disable; progress_list.lock    }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );  }  // deferred[ resolve | reject | notify ]  deferred[ tuple[ 0 ] ] = function() {    deferred[ tuple[ 0 ] + "With" ]( this === deferred ? promise : this, arguments );    return this;  };  deferred[ tuple[ 0 ] + "With" ] = list.fireWith;} );

Use jQuery. each to traverse the tuples array and perform related operations. For example, we use the first element in the tuples array as an example:

['Resolve', 'done', jQuery. Callbacks ('Once memory '), 'resolved']

Step 1: The declared Variable list points to the object returned by jQuery. Callbacks, And the stateString value is 'refreshed'

Step 2: add the 'done' attribute to promise and point to list. add (fail and progress point to their custom event objects) in step 1)

Step 3: Judge the stateString value. If it is in the 'recordted' or 'objected' state, add three event functions to the corresponding list:

  • -- Function for changing the state
  • -- Disable the processing of the corresponding status. For example, after 'refered', the rejected status will not be triggered, and vice versa.
  • -- The pending state is disabled. If the pending state is 'refered' or 'objected', the deferred state will definitely not be pending.

Step 4: add the fire-related methods to trigger the respective States ('refered', 'objected', 'Pending') for the object deferred:

  • -- Resolve, resolveWith
  • -- Reject and rejectWith
  • -- Policy, policywith

Okay, jQuery. each (tuples, function (I, tuple ){...}) This is the end of the explanation.

Summary:

Use jQuery. each to traverse tuples, add the three state operation values done, fail, and progress in tuples to the promise object, and point to the add method in the custom object respectively. If the status is resolved or rejected, add the three specific functions to the list of their custom objects. Then, the trigger event of the three States is assigned to the deferred object.

So far, the promise and deferred objects are shown in:

 

We mentioned the promise attribute of the promise object, that is, the extended promise object. Let's review the following:

 

So next, promise. promise (deferred) in the source code is the extended deferred object, so that the original deferred with only six triggering attributes has all the attributes of the promise object.

Then, func. call (deferred, deferred) is the execution parameter func. Of course, the premise is that func has a value. It is worth noting that deferred is used as the execution object and execution parameter of func, which is embodied in promise. then (I will explain it later ).

Finally, $. Deferred returns the constructed deferred object.

At this point, the entire deferred process has been completed.

Iii. Elaborate on promise. then

Promise. then source code is as follows:

promise = {  then: function( /* fnDone, fnFail, fnProgress */ ) {      var fns = arguments;    return jQuery.Deferred( function( newDefer ) {      jQuery.each( tuples, function( i, tuple ) {        var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];        // deferred[ done | fail | progress ] for forwarding actions to newDefer        deferred[ tuple[ 1 ] ]( function() {          var returned = fn && fn.apply( this, arguments );          if ( returned && jQuery.isFunction( returned.promise ) ) {            returned.promise()              .progress( newDefer.notify )              .done( newDefer.resolve )              .fail( newDefer.reject );          } else {            newDefer[ tuple[ 0 ] + "With" ](              this === promise ? newDefer.promise() : this,              fn ? [ returned ] : arguments            );          }        } );      } );      fns = null;    } ).promise();  }}

The source code for streamlining promise. then is as follows:

promise = {  then: function( /* fnDone, fnFail, fnProgress */ ) {      var fns = arguments;    return jQuery.Deferred( function( newDefer ) {          ...        } ).promise();  }}

The overall architecture clearly shows that promise. the then method uses jQuery. deferred returns a new restricted deferred object, that is, deferred. promise, because of this, after the then method is executed, we cannot use deferred. pomise manually triggers resolve, reject, or consumer y.

Next, we will further analyze the promise. then source code.

Var fns = arguments is to assign the parameters in the then method to fns and use them in jQuery. each. Next, we will use jQuery. deferred returns a constructed deferred object, but note that in jQuery. in Deferred, there is a parameter-Anonymous function. Remember that at the end of the previous section, we said that if jQuery. if there is a value in Deferred, execute it and pass the constructed deferred as the execution object and parameter:

Solid, newDefer in promise. then method points to the Deferred built through jQuery. deferred.

Next, jQuery. each (tuples, function (I, tuple ){...}) Processing, the focus is deferred [tuple [1] (function (){...});, Note that the deferred here is the parent deferred of the then method, as shown below:

Tuple [1] is-done | fail | progress. As we have discussed earlier, they point to the add method of their custom event objects. Therefore, we can understand why then method events are triggered after deferred. resolve | reject | condition y is followed by then, as shown below:

However, the then method is followed by the then method. How can this problem be solved?

It determines the return value of the callback function in the then method. If it is a deferred object, it triggers related events in the deferred object created by the then method, add it to the list corresponding to the deferred object returned by the callback function. In this way, when the trigger event in the callback function is triggered, the deferred object of the then method is triggered, if the then method has a then method, it is associated.

Well, what if the return value of the callback function in the then method is a non-deferred object? Then it carries the returned value and directly triggers events related to the deferred object created by the then method. Therefore, if the then method has a then method, it is associated.

All right, the solution of promise. then is basically complete.

Iv. Thinking

Have you found out that promise. then associates the parent Deferred with the deferred variable in jQuery. deferred through the scope chain. If you still remember the single-chain table in the data structure, do you find it familiar? The author uses jQuery here. the Deferred factory builds each deferred and uses the scope chain to associate each other, just like a single-chain table.

Therefore, with this idea, we can simulate a very simple Deferred called SimpleDef. Every time we execute the SimpleDef function, it returns a constructed simpleDef object, which contains three methods: done, then, and fire:

  • -- Done is like the add method. It adds the parameters in done to its parent simpleDef list and returns the parent simpleDef object;
  • -- Then is to add its parameter func to the list of the parent SimpleDef object and return a new SimpleDef object;
  • -- Fire is to trigger all functions in the list of corresponding simpleDef objects.

The implementation code is as follows:

 function SimpleDef(){  var list = [],    simpleDef = {      done: function(func){        list.push(func);        return simpleDef;      },      then: function(func){        list.push(func);        return SimpleDef();      },      fire: function(){        var i = list.length;        while(i--){          list[i]();        }      }    };  return simpleDef;}

The test code is as follows:

var def = SimpleDef();var then1 = def.done(function(){  console.log('self1-done1');}).done(function(){  console.log('self1-done2');}).then(function(){  console.log('self2-then1');}).done(function(){  console.log('self2-done1');});def.fire();//=>self2-then1 self1-done2 self1-done1console.log('xxxxxxxxxxxxxxxxxxxx');then1.fire();//=>self2-done1

The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.