Skip to content

Commit

Permalink
Deferred: Warn on exceptions that are likely programming errors
Browse files Browse the repository at this point in the history
Fixes gh-2736
Closes gh-2737
  • Loading branch information
dmethvin committed Jan 13, 2016
1 parent bdf1b8f commit 36a7cf9
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 1 deletion.
13 changes: 12 additions & 1 deletion src/deferred.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 );
}
};
Expand Down
19 changes: 19 additions & 0 deletions src/deferred/exceptionHook.js
Original file line number Diff line number Diff line change
@@ -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 );
}
};

} );
1 change: 1 addition & 0 deletions src/jquery.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ define( [
"./traversing",
"./callbacks",
"./deferred",
"./deferred/exceptionHook",
"./core/ready",
"./data",
"./queue",
Expand Down
59 changes: 59 additions & 0 deletions test/unit/deferred.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down

0 comments on commit 36a7cf9

Please sign in to comment.