From 35295f1c20d6f4ce724840ecad00d347ca5a1295 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 14 Apr 2015 13:30:58 -0400 Subject: [PATCH] Deferred: Always handle progress callbacks before done/fail Fixes gh-2013 Fixes gh-2010 Closes gh-2210 (cherry picked from commit 002240a6eb1cee2fcd886d5cf44893eb67f246f1) --- src/deferred.js | 61 +++++++++++++++++++++++-------------------- test/unit/deferred.js | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 29 deletions(-) diff --git a/src/deferred.js b/src/deferred.js index a45dad88b..6ec9eec06 100644 --- a/src/deferred.js +++ b/src/deferred.js @@ -15,13 +15,14 @@ jQuery.extend({ Deferred: function( func ) { var tuples = [ - // action, add listener, callbacks, .then handlers, final state + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks("memory"), + jQuery.Callbacks("memory"), 2 ], [ "resolve", "done", jQuery.Callbacks("once memory"), - jQuery.Callbacks("once memory"), "resolved" ], + jQuery.Callbacks("once memory"), 0, "resolved" ], [ "reject", "fail", jQuery.Callbacks("once memory"), - jQuery.Callbacks("once memory"), "rejected" ], - [ "notify", "progress", jQuery.Callbacks("memory"), - jQuery.Callbacks("memory") ] + jQuery.Callbacks("once memory"), 1, "rejected" ] ], state = "pending", promise = { @@ -38,17 +39,19 @@ jQuery.extend({ return jQuery.Deferred(function( newDefer ) { jQuery.each( tuples, function( i, tuple ) { - var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) // deferred.done(function() { bind to newDefer or newDefer.resolve }) // deferred.fail(function() { bind to newDefer or newDefer.reject }) - // deferred.progress(function() { bind to newDefer or newDefer.notify }) 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 ) - .progress( newDefer.notify ); + .fail( newDefer.reject ); } else { newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, @@ -176,37 +179,37 @@ jQuery.extend({ } return jQuery.Deferred(function( newDefer ) { - // fulfilled_handlers.add( ... ) + // progress_handlers.add( ... ) tuples[ 0 ][ 3 ].add( resolve( 0, newDefer, - jQuery.isFunction( onFulfilled ) ? - onFulfilled : - Identity + jQuery.isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith ) ); - // rejected_handlers.add( ... ) + // fulfilled_handlers.add( ... ) tuples[ 1 ][ 3 ].add( resolve( 0, newDefer, - jQuery.isFunction( onRejected ) ? - onRejected : - Thrower + jQuery.isFunction( onFulfilled ) ? + onFulfilled : + Identity ) ); - // progress_handlers.add( ... ) + // rejected_handlers.add( ... ) tuples[ 2 ][ 3 ].add( resolve( 0, newDefer, - jQuery.isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith + jQuery.isFunction( onRejected ) ? + onRejected : + Thrower ) ); }).promise(); @@ -222,11 +225,11 @@ jQuery.extend({ // Add list-specific methods jQuery.each( tuples, function( i, tuple ) { var list = tuple[ 2 ], - stateString = tuple[ 4 ]; + stateString = tuple[ 5 ]; + // promise.progress = list.add // promise.done = list.add // promise.fail = list.add - // promise.progress = list.add promise[ tuple[1] ] = list.add; // Handle state @@ -240,29 +243,29 @@ jQuery.extend({ // rejected_callbacks.disable // fulfilled_callbacks.disable - tuples[ i ^ 1 ][ 2 ].disable, + tuples[ 3 - i ][ 2 ].disable, // progress_callbacks.lock - tuples[ 2 ][ 2 ].lock + tuples[ 0 ][ 2 ].lock ); } + // progress_handlers.fire // fulfilled_handlers.fire // rejected_handlers.fire - // progress_handlers.fire list.add( tuple[ 3 ].fire ); + // deferred.notify = function() { deferred.notifyWith(...) } // deferred.resolve = function() { deferred.resolveWith(...) } // deferred.reject = function() { deferred.rejectWith(...) } - // deferred.notify = function() { deferred.notifyWith(...) } deferred[ tuple[0] ] = function() { deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); return this; }; + // deferred.notifyWith = list.fireWith // deferred.resolveWith = list.fireWith // deferred.rejectWith = list.fireWith - // deferred.notifyWith = list.fireWith deferred[ tuple[0] + "With" ] = list.fireWith; }); diff --git a/test/unit/deferred.js b/test/unit/deferred.js index c2dcf33d0..97f9111dd 100644 --- a/test/unit/deferred.js +++ b/test/unit/deferred.js @@ -565,6 +565,54 @@ test( "jQuery.Deferred.then - progress and thenables", function( assert ) { trigger.notify(); }); +test( "jQuery.Deferred - notify and resolve", function( assert ) { + + expect( 7 ); + + var notifiedResolved = jQuery.Deferred().notify( "foo" )/*xxx .resolve( "bar" )*/, + done = jQuery.map( new Array( 3 ), function() { return assert.async(); } ); + + notifiedResolved.progress( function( v ) { + assert.strictEqual( v, "foo", "progress value" ); + } ); + + notifiedResolved.pipe().progress( function( v ) { + assert.strictEqual( v, "foo", "piped progress value" ); + } ); + + notifiedResolved.pipe( null, null, function() { + return "baz"; + } ).progress( function( v ) { + assert.strictEqual( v, "baz", "replaced piped progress value" ); + } ); + + notifiedResolved.pipe( null, null, function() { + return jQuery.Deferred().notify( "baz" ).resolve( "quux" ); + } ).progress( function( v ) { + assert.strictEqual( v, "baz", "deferred replaced piped progress value" ); + } ); + + notifiedResolved.then().progress( function( v ) { + assert.strictEqual( v, "foo", "then'd progress value" ); + done.pop().call(); + } ); + + notifiedResolved.then( null, null, function() { + return "baz"; + } ).progress( function( v ) { + assert.strictEqual( v, "baz", "replaced then'd progress value" ); + done.pop().call(); + } ); + + notifiedResolved.then( null, null, function() { + return jQuery.Deferred().notify( "baz" ).resolve( "quux" ); + } ).progress( function( v ) { + // Progress from the surrogate deferred is ignored + assert.strictEqual( v, "quux", "deferred replaced then'd progress value" ); + done.pop().call(); + } ); +}); + test( "jQuery.when", function() { expect( 37 ); -- 2.39.5