Skip to content

Commit 36a7cf9

Browse files
committedJan 13, 2016
Deferred: Warn on exceptions that are likely programming errors
Fixes gh-2736 Closes gh-2737
1 parent bdf1b8f commit 36a7cf9

File tree

4 files changed

+91
-1
lines changed

4 files changed

+91
-1
lines changed
 

‎src/deferred.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,17 @@ jQuery.extend( {
157157
mightThrow();
158158
} catch ( e ) {
159159

160+
if ( jQuery.Deferred.exceptionHook ) {
161+
jQuery.Deferred.exceptionHook( e,
162+
process.stackTrace );
163+
}
164+
160165
// Support: Promises/A+ section 2.3.3.3.4.1
161166
// https://promisesaplus.com/#point-61
162167
// Ignore post-resolution exceptions
163168
if ( depth + 1 >= maxDepth ) {
164169

165-
// Only substitue handlers pass on context
170+
// Only substitute handlers pass on context
166171
// and multiple values (non-spec behavior)
167172
if ( handler !== Thrower ) {
168173
that = undefined;
@@ -182,6 +187,12 @@ jQuery.extend( {
182187
if ( depth ) {
183188
process();
184189
} else {
190+
191+
// Call an optional hook to record the stack, in case of exception
192+
// since it's otherwise lost when execution goes async
193+
if ( jQuery.Deferred.getStackHook ) {
194+
process.stackTrace = jQuery.Deferred.getStackHook();
195+
}
185196
window.setTimeout( process );
186197
}
187198
};

‎src/deferred/exceptionHook.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
define( [
2+
"../core",
3+
"../deferred"
4+
], function( jQuery ) {
5+
6+
// These usually indicate a programmer mistake during development,
7+
// warn about them ASAP rather than swallowing them by default.
8+
var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
9+
10+
jQuery.Deferred.exceptionHook = function( error, stack ) {
11+
12+
// Support: IE9
13+
// Console exists when dev tools are open, which can happen at any time
14+
if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
15+
window.console.warn( "jQuery.Deferred exception: " + error.message, stack );
16+
}
17+
};
18+
19+
} );

‎src/jquery.js

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ define( [
44
"./traversing",
55
"./callbacks",
66
"./deferred",
7+
"./deferred/exceptionHook",
78
"./core/ready",
89
"./data",
910
"./queue",

‎test/unit/deferred.js

+59
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,65 @@ QUnit.test( "jQuery.Deferred.then - spec compatibility", function( assert ) {
525525
} catch ( _ ) {}
526526
} );
527527

528+
QUnit[ window.console ? "test" : "skip" ]( "jQuery.Deferred.exceptionHook", function( assert ) {
529+
530+
assert.expect( 1 );
531+
532+
var done = assert.async(),
533+
defer = jQuery.Deferred(),
534+
oldWarn = window.console.warn;
535+
536+
window.console.warn = function( msg ) {
537+
assert.ok( /barf/.test( msg ), "Message: " + msg );
538+
};
539+
jQuery.when(
540+
defer.then( function() {
541+
// Should get an error
542+
jQuery.barf();
543+
} ).then( null, jQuery.noop ),
544+
defer.then( function() {
545+
// Should NOT get an error
546+
throw new Error( "Make me a sandwich" );
547+
} ).then( null, jQuery.noop )
548+
).then( function( ) {
549+
window.console.warn = oldWarn;
550+
done();
551+
} );
552+
553+
defer.resolve();
554+
} );
555+
556+
QUnit[ window.console ? "test" : "skip" ]( "jQuery.Deferred.exceptionHook with stack hooks", function( assert ) {
557+
558+
assert.expect( 2 );
559+
560+
var done = assert.async(),
561+
defer = jQuery.Deferred(),
562+
oldWarn = window.console.warn;
563+
564+
jQuery.Deferred.getStackHook = function() {
565+
// Default exceptionHook assumes the stack is in a form console.warn can log,
566+
// but a custom getStackHook+exceptionHook pair could save a raw form and
567+
// format it to a string only when an exception actually occurs.
568+
// For the unit test we just ensure the plumbing works.
569+
return "NO STACK FOR YOU";
570+
};
571+
572+
window.console.warn = function( msg, stack ) {
573+
assert.ok( /cough_up_hairball/.test( msg ), "Function mentioned: " + msg );
574+
assert.ok( /NO STACK FOR YOU/.test( stack ), "Stack trace included: " + stack );
575+
};
576+
defer.then( function() {
577+
jQuery.cough_up_hairball();
578+
} ).then( null, function( ) {
579+
window.console.warn = oldWarn;
580+
delete jQuery.Deferred.getStackHook;
581+
done();
582+
} );
583+
584+
defer.resolve();
585+
} );
586+
528587
QUnit.test( "jQuery.Deferred - 1.x/2.x compatibility", function( assert ) {
529588

530589
assert.expect( 8 );

0 commit comments

Comments
 (0)