]> source.dussan.org Git - jquery.git/commitdiff
Deferred: Warn on exceptions that are likely programming errors
authorDave Methvin <dave.methvin@gmail.com>
Mon, 23 Nov 2015 18:57:10 +0000 (13:57 -0500)
committerDave Methvin <dave.methvin@gmail.com>
Wed, 13 Jan 2016 17:39:58 +0000 (12:39 -0500)
Fixes gh-2736
Closes gh-2737

src/deferred.js
src/deferred/exceptionHook.js [new file with mode: 0644]
src/jquery.js
test/unit/deferred.js

index ff12ac3e0cf2efe063c26c9c7f53de1a759ea296..6a1ef3b43895448aab29079743a3eb21245c903e 100644 (file)
@@ -157,12 +157,17 @@ jQuery.extend( {
                                                                                        mightThrow();
                                                                                } catch ( e ) {
 
+                                                                                       if ( jQuery.Deferred.exceptionHook ) {
+                                                                                               jQuery.Deferred.exceptionHook( e,
+                                                                                                       process.stackTrace );
+                                                                                       }
+
                                                                                        // Support: Promises/A+ section 2.3.3.3.4.1
                                                                                        // https://promisesaplus.com/#point-61
                                                                                        // Ignore post-resolution exceptions
                                                                                        if ( depth + 1 >= maxDepth ) {
 
-                                                                                               // Only substitue handlers pass on context
+                                                                                               // Only substitute handlers pass on context
                                                                                                // and multiple values (non-spec behavior)
                                                                                                if ( handler !== Thrower ) {
                                                                                                        that = undefined;
@@ -182,6 +187,12 @@ jQuery.extend( {
                                                        if ( depth ) {
                                                                process();
                                                        } else {
+
+                                                               // Call an optional hook to record the stack, in case of exception
+                                                               // since it's otherwise lost when execution goes async
+                                                               if ( jQuery.Deferred.getStackHook ) {
+                                                                       process.stackTrace = jQuery.Deferred.getStackHook();
+                                                               }
                                                                window.setTimeout( process );
                                                        }
                                                };
diff --git a/src/deferred/exceptionHook.js b/src/deferred/exceptionHook.js
new file mode 100644 (file)
index 0000000..b995506
--- /dev/null
@@ -0,0 +1,19 @@
+define( [
+       "../core",
+       "../deferred"
+], function( jQuery ) {
+
+// These usually indicate a programmer mistake during development,
+// warn about them ASAP rather than swallowing them by default.
+var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
+
+jQuery.Deferred.exceptionHook = function( error, stack ) {
+
+       // Support: IE9
+       // Console exists when dev tools are open, which can happen at any time
+       if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
+               window.console.warn( "jQuery.Deferred exception: " + error.message, stack );
+       }
+};
+
+} );
index 2faa9c3cdae8ea9bef3e6f94bef2d045bdcfd95a..4cc9c8a909f78bcad1b79f784d97294781292f7a 100644 (file)
@@ -4,6 +4,7 @@ define( [
        "./traversing",
        "./callbacks",
        "./deferred",
+       "./deferred/exceptionHook",
        "./core/ready",
        "./data",
        "./queue",
index 83c2f4f6bd4c190b61dcd8f7c0f6ad73b9db5df1..d65ce34ca9562a9d68ed67be60c44d32533c2cba 100644 (file)
@@ -525,6 +525,65 @@ QUnit.test( "jQuery.Deferred.then - spec compatibility", function( assert ) {
        } catch ( _ ) {}
 } );
 
+QUnit[ window.console ? "test" : "skip" ]( "jQuery.Deferred.exceptionHook", function( assert ) {
+
+       assert.expect( 1 );
+
+       var done = assert.async(),
+               defer = jQuery.Deferred(),
+               oldWarn = window.console.warn;
+
+       window.console.warn = function( msg ) {
+               assert.ok( /barf/.test( msg ), "Message: " + msg );
+       };
+       jQuery.when(
+               defer.then( function() {
+                       // Should get an error
+                       jQuery.barf();
+               } ).then( null, jQuery.noop ),
+               defer.then( function() {
+                       // Should NOT get an error
+                       throw new Error( "Make me a sandwich" );
+               } ).then( null, jQuery.noop )
+       ).then( function( ) {
+               window.console.warn = oldWarn;
+               done();
+       } );
+
+       defer.resolve();
+} );
+
+QUnit[ window.console ? "test" : "skip" ]( "jQuery.Deferred.exceptionHook with stack hooks", function( assert ) {
+
+       assert.expect( 2 );
+
+       var done = assert.async(),
+               defer = jQuery.Deferred(),
+               oldWarn = window.console.warn;
+
+       jQuery.Deferred.getStackHook = function() {
+               // Default exceptionHook assumes the stack is in a form console.warn can log,
+               // but a custom getStackHook+exceptionHook pair could save a raw form and
+               // format it to a string only when an exception actually occurs.
+               // For the unit test we just ensure the plumbing works.
+               return "NO STACK FOR YOU";
+       };
+
+       window.console.warn = function( msg, stack ) {
+               assert.ok( /cough_up_hairball/.test( msg ), "Function mentioned: " + msg );
+               assert.ok( /NO STACK FOR YOU/.test( stack ), "Stack trace included: " + stack );
+       };
+       defer.then( function() {
+               jQuery.cough_up_hairball();
+       } ).then( null, function( ) {
+               window.console.warn = oldWarn;
+               delete jQuery.Deferred.getStackHook;
+               done();
+       } );
+
+       defer.resolve();
+} );
+
 QUnit.test( "jQuery.Deferred - 1.x/2.x compatibility", function( assert ) {
 
        assert.expect( 8 );