]> source.dussan.org Git - jquery.git/commitdiff
Core: implement ready without Deferred
authorTimmy Willison <timmywillisn@gmail.com>
Tue, 19 Jan 2016 19:47:52 +0000 (14:47 -0500)
committerTimmy Willison <timmywillisn@gmail.com>
Mon, 4 Apr 2016 15:26:22 +0000 (11:26 -0400)
- Make jQuery.ready promise-compatible
- Gives up sync guarantee for post-ready callbacks

Fixes gh-1778
Fixes gh-1823
Close gh-2891

Gruntfile.js
build/tasks/build.js
src/core/ready-no-deferred.js [new file with mode: 0644]
src/core/ready.js
test/unit/ready.js

index 723bc12cc850ab70e60f2baa6b2bb8a1f94a1a9d..c979f282217ef9efa4ab7e2ddf44877c2d8d3bbd 100644 (file)
@@ -63,6 +63,10 @@ module.exports = function( grunt ) {
                                        callbacks: [ "deferred" ],
                                        css: [ "effects", "dimensions", "offset" ],
                                        "css/showHide": [ "effects" ],
+                                       deferred: {
+                                               remove: [ "ajax", "effects", "queue", "core/ready" ],
+                                               include: [ "core/ready-no-deferred" ]
+                                       },
                                        sizzle: [ "css/hiddenVisibleSelectors", "effects/animatedSelector" ]
                                }
                        }
index 1e1c6bb3769c22f057a1d8385d9159385283a558..a62f04ab194a23989cfe31d024b8e35f73c624c0 100644 (file)
@@ -168,7 +168,8 @@ module.exports = function( grunt ) {
                         *  whether it should included or excluded
                         */
                        excluder = function( flag ) {
-                               var m = /^(\+|\-|)([\w\/-]+)$/.exec( flag ),
+                               var additional,
+                                       m = /^(\+|\-|)([\w\/-]+)$/.exec( flag ),
                                        exclude = m[ 1 ] === "-",
                                        module = m[ 2 ];
 
@@ -192,8 +193,16 @@ module.exports = function( grunt ) {
                                                        }
                                                }
 
+                                               additional = removeWith[ module ];
+
                                                // Check removeWith list
-                                               excludeList( removeWith[ module ] );
+                                               if ( additional ) {
+                                                       excludeList( additional.remove || additional );
+                                                       if ( additional.include ) {
+                                                               included = included.concat( additional.include );
+                                                               grunt.log.writeln( "+" + additional.include );
+                                                       }
+                                               }
                                        } else {
                                                grunt.log.error( "Module \"" + module + "\" is a minimum requirement." );
                                                if ( module === "selector" ) {
diff --git a/src/core/ready-no-deferred.js b/src/core/ready-no-deferred.js
new file mode 100644 (file)
index 0000000..66f209b
--- /dev/null
@@ -0,0 +1,109 @@
+define( [
+       "../core",
+       "../var/document"
+], function( jQuery, document ) {
+
+var readyCallbacks = [],
+       readyFiring = false,
+       whenReady = function( fn ) {
+               readyCallbacks.push( fn );
+       },
+       executeReady = function( fn ) {
+
+               // Prevent errors from freezing future callback execution (gh-1823)
+               // Not backwards-compatible as this does not execute sync
+               window.setTimeout( function() {
+                       fn.call( document, jQuery );
+               } );
+       };
+
+jQuery.fn.ready = function( fn ) {
+       whenReady( fn );
+       return this;
+};
+
+jQuery.extend( {
+
+       // Is the DOM ready to be used? Set to true once it occurs.
+       isReady: false,
+
+       // A counter to track how many items to wait for before
+       // the ready event fires. See #6781
+       readyWait: 1,
+
+       // Hold (or release) the ready event
+       holdReady: function( hold ) {
+               if ( hold ) {
+                       jQuery.readyWait++;
+               } else {
+                       jQuery.ready( true );
+               }
+       },
+
+       ready: function( wait ) {
+
+               // Abort if there are pending holds or we're already ready
+               if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+                       return;
+               }
+
+               // Remember that the DOM is ready
+               jQuery.isReady = true;
+
+               // If a normal DOM Ready event fired, decrement, and wait if need be
+               if ( wait !== true && --jQuery.readyWait > 0 ) {
+                       return;
+               }
+
+               whenReady = function( fn ) {
+                       readyCallbacks.push( fn );
+
+                       if ( !readyFiring ) {
+                               readyFiring = true;
+
+                               while ( readyCallbacks.length ) {
+                                       fn = readyCallbacks.shift();
+                                       if ( jQuery.isFunction( fn ) ) {
+                                               executeReady( fn );
+                                       }
+                               }
+                               readyFiring = false;
+                       }
+               };
+
+               whenReady();
+       }
+} );
+
+// Make jQuery.ready Promise consumable (gh-1778)
+jQuery.ready.then = jQuery.fn.ready;
+
+/**
+ * The ready event handler and self cleanup method
+ */
+function completed() {
+       document.removeEventListener( "DOMContentLoaded", completed );
+       window.removeEventListener( "load", completed );
+       jQuery.ready();
+}
+
+// Catch cases where $(document).ready() is called
+// after the browser event has already occurred.
+// Support: IE9-10 only
+// Older IE sometimes signals "interactive" too soon
+if ( document.readyState === "complete" ||
+       ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
+
+       // Handle it asynchronously to allow scripts the opportunity to delay ready
+       window.setTimeout( jQuery.ready );
+
+} else {
+
+       // Use the handy event callback
+       document.addEventListener( "DOMContentLoaded", completed );
+
+       // A fallback to window.onload, that will always work
+       window.addEventListener( "load", completed );
+}
+
+} );
index d8c688832c2db7b720d441775dfd374933325a15..b98ff5ba0597f5dbf0837cd356421f13fb2ae238 100644 (file)
@@ -5,12 +5,11 @@ define( [
 ], function( jQuery, document ) {
 
 // The deferred used on DOM ready
-var readyList;
+var readyList = jQuery.Deferred();
 
 jQuery.fn.ready = function( fn ) {
 
-       // Add the callback
-       jQuery.ready.promise().done( fn );
+       readyList.then( fn );
 
        return this;
 };
@@ -54,43 +53,32 @@ jQuery.extend( {
        }
 } );
 
-/**
- * The ready event handler and self cleanup method
- */
+jQuery.ready.then = readyList.then;
+
+// The ready event handler and self cleanup method
 function completed() {
        document.removeEventListener( "DOMContentLoaded", completed );
        window.removeEventListener( "load", completed );
        jQuery.ready();
 }
 
-jQuery.ready.promise = function( obj ) {
-       if ( !readyList ) {
-
-               readyList = jQuery.Deferred();
+// Catch cases where $(document).ready() is called
+// after the browser event has already occurred.
+// Support: IE <=9 - 10 only
+// Older IE sometimes signals "interactive" too soon
+if ( document.readyState === "complete" ||
+       ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
 
-               // Catch cases where $(document).ready() is called
-               // after the browser event has already occurred.
-               // Support: IE <=9 - 10 only
-               // Older IE sometimes signals "interactive" too soon
-               if ( document.readyState === "complete" ||
-                       ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
+       // Handle it asynchronously to allow scripts the opportunity to delay ready
+       window.setTimeout( jQuery.ready );
 
-                       // Handle it asynchronously to allow scripts the opportunity to delay ready
-                       window.setTimeout( jQuery.ready );
+} else {
 
-               } else {
+       // Use the handy event callback
+       document.addEventListener( "DOMContentLoaded", completed );
 
-                       // Use the handy event callback
-                       document.addEventListener( "DOMContentLoaded", completed );
-
-                       // A fallback to window.onload, that will always work
-                       window.addEventListener( "load", completed );
-               }
-       }
-       return readyList.promise( obj );
-};
-
-// Kick off the DOM ready check even if the user does not
-jQuery.ready.promise();
+       // A fallback to window.onload, that will always work
+       window.addEventListener( "load", completed );
+}
 
 } );
index 6272dbdd403304f5dd17f3e523c3a4607bc4060c..84bcc2c9eb9bc65aa66551bb5ab2c7ae3d667897 100644 (file)
@@ -2,6 +2,7 @@ QUnit.module( "ready" );
 
 ( function() {
        var notYetReady, noEarlyExecution,
+               promisified = Promise.resolve( jQuery.ready ),
                order = [],
                args = {};
 
@@ -26,13 +27,36 @@ QUnit.module( "ready" );
                };
        }
 
+       function throwError( num ) {
+
+               // Not a global QUnit failure
+               var onerror = window.onerror;
+               window.onerror = function() {
+                       window.onerror = onerror;
+               };
+
+               throw new Error( "Ready error " + num );
+       }
+
        // Bind to the ready event in every possible way.
        jQuery( makeHandler( "a" ) );
        jQuery( document ).ready( makeHandler( "b" ) );
 
+       // Throw in an error to ensure other callbacks are called
+       jQuery( function() {
+               throwError( 1 );
+       } );
+
+       // Throw two errors in a row
+       jQuery( function() {
+               throwError( 2 );
+       } );
+       jQuery.when( jQuery.ready ).done( makeHandler( "c" ) );
+
        // Do it twice, just to be sure.
-       jQuery( makeHandler( "c" ) );
-       jQuery( document ).ready( makeHandler( "d" ) );
+       jQuery( makeHandler( "d" ) );
+       jQuery( document ).ready( makeHandler( "e" ) );
+       jQuery.when( jQuery.ready ).done( makeHandler( "f" ) );
 
        noEarlyExecution = order.length === 0;
 
@@ -44,7 +68,7 @@ QUnit.module( "ready" );
                        "Handlers bound to DOM ready should not execute before DOM ready" );
 
                // Ensure execution order.
-               assert.deepEqual( order, [ "a", "b", "c", "d" ],
+               assert.deepEqual( order, [ "a", "b", "c", "d", "e", "f" ],
                        "Bound DOM ready handlers should execute in on-order" );
 
                // Ensure handler argument is correct.
@@ -55,16 +79,48 @@ QUnit.module( "ready" );
 
                order = [];
 
-               // Now that the ready event has fired, again bind to the ready event
-               // in every possible way. These event handlers should execute immediately.
+               // Now that the ready event has fired, again bind to the ready event.
+               // These ready handlers should execute asynchronously.
+               var done = assert.async();
                jQuery( makeHandler( "g" ) );
-               assert.equal( order.pop(), "g", "Event handler should execute immediately" );
-               assert.equal( args.g, jQuery, "Argument passed to fn in jQuery( fn ) should be jQuery" );
-
                jQuery( document ).ready( makeHandler( "h" ) );
-               assert.equal( order.pop(), "h", "Event handler should execute immediately" );
-               assert.equal( args.h, jQuery,
-                       "Argument passed to fn in jQuery(document).ready( fn ) should be jQuery" );
+               window.setTimeout( function() {
+                       assert.equal( order.shift(), "g", "Event handler should execute immediately, but async" );
+                       assert.equal( args.g, jQuery, "Argument passed to fn in jQuery( fn ) should be jQuery" );
+
+                       assert.equal( order.shift(), "h", "Event handler should execute immediately, but async" );
+                       assert.equal( args.h, jQuery,
+                               "Argument passed to fn in jQuery(document).ready( fn ) should be jQuery" );
+                       done();
+               } );
        } );
 
+       QUnit.test( "Promise.resolve(jQuery.ready)", function( assert ) {
+               assert.expect( 2 );
+               var done = jQuery.map( new Array( 2 ), function() { return assert.async(); } );
+
+               promisified.then( function() {
+                       assert.ok( jQuery.isReady, "Native promised resolved" );
+                       done.pop()();
+               } );
+
+               Promise.resolve( jQuery.ready ).then( function() {
+                       assert.ok( jQuery.isReady, "Native promised resolved" );
+                       done.pop()();
+               } );
+       } );
+
+       QUnit.test( "Error in ready callback does not halt all future executions (gh-1823)", function( assert ) {
+               assert.expect( 1 );
+               var done = assert.async();
+
+               jQuery( function() {
+                       throwError( 3 );
+               } );
+
+               jQuery( function() {
+                       assert.ok( true, "Subsequent handler called" );
+                       done();
+               } );
+       } );
 } )();