diff --git a/src/deferred.js b/src/deferred.js index 90f9c808..feaccaef 100644 --- a/src/deferred.js +++ b/src/deferred.js @@ -1,7 +1,7 @@ (function( jQuery ) { var // Promise methods - promiseMethods = "then done fail isResolved isRejected promise".split( " " ), + promiseMethods = "done fail isResolved isRejected promise then always chain".split( " " ), // Static reference to slice sliceDeferred = [].slice; @@ -100,10 +100,38 @@ jQuery.extend({ deferred.done( doneCallbacks ).fail( failCallbacks ); return this; }, + always: function() { + return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments ); + }, fail: failDeferred.done, rejectWith: failDeferred.resolveWith, reject: failDeferred.resolve, isRejected: failDeferred.isResolved, + // Chain + chain: function( fnDone, fnFail ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ] + }, function( handler, data ) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if ( jQuery.isFunction( fn ) ) { + deferred[ handler ](function() { + returned = fn.apply( this, arguments ); + if ( jQuery.isFunction( returned.promise ) ) { + returned.promise().then( newDefer.resolve, newDefer.reject ); + } else { + newDefer[ action ]( returned ); + } + }); + } else { + deferred[ handler ]( newDefer[ action ] ); + } + } ); + }).promise(); + }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { @@ -169,4 +197,4 @@ jQuery.extend({ } }); -})( jQuery ); +})( jQuery ); \ No newline at end of file diff --git a/test/unit/deferred.js b/test/unit/deferred.js index 6ba4767a..c1ed7c57 100644 --- a/test/unit/deferred.js +++ b/test/unit/deferred.js @@ -1,153 +1,273 @@ module("deferred", { teardown: moduleTeardown }); -test("jQuery._Deferred()", function() { +jQuery.each( [ "", " - new operator" ], function( _, withNew ) { - expect( 11 ); - - var deferred, - object, - test; - - deferred = jQuery._Deferred(); - - test = false; - - deferred.done( function( value ) { - equals( value , "value" , "Test pre-resolve callback" ); - test = true; - } ); - - deferred.resolve( "value" ); - - ok( test , "Test pre-resolve callbacks called right away" ); - - test = false; - - deferred.done( function( value ) { - equals( value , "value" , "Test post-resolve callback" ); - test = true; - } ); - - ok( test , "Test post-resolve callbacks called right away" ); - - deferred.cancel(); - - test = true; - - deferred.done( function() { - ok( false , "Cancel was ignored" ); - test = false; - } ); - - ok( test , "Test cancel" ); - - deferred = jQuery._Deferred().resolve(); - - try { - deferred.done( function() { - throw "Error"; - } , function() { - ok( true , "Test deferred do not cancel on exception" ); - } ); - } catch( e ) { - strictEqual( e , "Error" , "Test deferred propagates exceptions"); - deferred.done(); + function createDeferred() { + return withNew ? new jQuery._Deferred() : jQuery._Deferred(); } - test = ""; - deferred = jQuery._Deferred().done( function() { + test("jQuery._Deferred" + withNew, function() { - test += "A"; + expect( 11 ); - }, function() { + var deferred, + object, + test; - test += "B"; + deferred = createDeferred(); - } ).resolve(); + test = false; - strictEqual( test , "AB" , "Test multiple done parameters" ); + deferred.done( function( value ) { + equals( value , "value" , "Test pre-resolve callback" ); + test = true; + } ); - test = ""; + deferred.resolve( "value" ); - deferred.done( function() { + ok( test , "Test pre-resolve callbacks called right away" ); + + test = false; + + deferred.done( function( value ) { + equals( value , "value" , "Test post-resolve callback" ); + test = true; + } ); + + ok( test , "Test post-resolve callbacks called right away" ); + + deferred.cancel(); + + test = true; + + deferred.done( function() { + ok( false , "Cancel was ignored" ); + test = false; + } ); + + ok( test , "Test cancel" ); + + deferred = createDeferred().resolve(); + + try { + deferred.done( function() { + throw "Error"; + } , function() { + ok( true , "Test deferred do not cancel on exception" ); + } ); + } catch( e ) { + strictEqual( e , "Error" , "Test deferred propagates exceptions"); + deferred.done(); + } + + test = ""; + deferred = createDeferred().done( function() { + + test += "A"; + + }, function() { + + test += "B"; + + } ).resolve(); + + strictEqual( test , "AB" , "Test multiple done parameters" ); + + test = ""; deferred.done( function() { - test += "C"; + deferred.done( function() { + test += "C"; + + } ); + + test += "A"; + + }, function() { + + test += "B"; } ); - test += "A"; + strictEqual( test , "ABC" , "Test done callbacks order" ); - }, function() { + deferred = createDeferred(); - test += "B"; + deferred.resolveWith( jQuery , [ document ] ).done( function( doc ) { + ok( this === jQuery && arguments.length === 1 && doc === document , "Test fire context & args" ); + }); + + // #8421 + deferred = createDeferred(); + deferred.resolveWith().done(function() { + ok( true, "Test resolveWith can be called with no argument" ); + }); + }); +} ); + +jQuery.each( [ "", " - new operator" ], function( _, withNew ) { + + function createDeferred( fn ) { + return withNew ? new jQuery.Deferred( fn ) : jQuery.Deferred( fn ); + } + + test("jQuery.Deferred" + withNew, function() { + + expect( 8 ); + + createDeferred().resolve().then( function() { + ok( true , "Success on resolve" ); + ok( this.isResolved(), "Deferred is resolved" ); + }, function() { + ok( false , "Error on resolve" ); + }).always( function() { + ok( true , "Always callback on resolve" ); + }); + + createDeferred().reject().then( function() { + ok( false , "Success on reject" ); + }, function() { + ok( true , "Error on reject" ); + ok( this.isRejected(), "Deferred is rejected" ); + }).always( function() { + ok( true , "Always callback on reject" ); + }); + + createDeferred( function( defer ) { + ok( this === defer , "Defer passed as this & first argument" ); + this.resolve( "done" ); + }).then( function( value ) { + strictEqual( value , "done" , "Passed function executed" ); + }); + }); +} ); + +test( "jQuery.Deferred.chain - filtering (done)", function() { + + expect(3); + + var defer = jQuery.Deferred(), + chained = defer.chain(function( a, b ) { + return a * b; + }), + value1, + value2, + value3; + + chained.done(function( result ) { + value3 = result; + }); + + defer.done(function( a, b ) { + value1 = a; + value2 = b; + }); + + defer.resolve( 2, 3 ); + + strictEqual( value1, 2, "first resolve value ok" ); + strictEqual( value2, 3, "second resolve value ok" ); + strictEqual( value3, 6, "result of filter ok" ); + + jQuery.Deferred().reject().chain(function() { + ok( false, "chain should not be called on reject" ); + }); +}); + +test( "jQuery.Deferred.chain - filtering (fail)", function() { + + expect(3); + + var defer = jQuery.Deferred(), + chained = defer.chain( null, function( a, b ) { + return a * b; + } ), + value1, + value2, + value3; + + chained.fail(function( result ) { + value3 = result; + }); + + defer.fail(function( a, b ) { + value1 = a; + value2 = b; + }); + + defer.reject( 2, 3 ); + + strictEqual( value1, 2, "first reject value ok" ); + strictEqual( value2, 3, "second reject value ok" ); + strictEqual( value3, 6, "result of filter ok" ); + + jQuery.Deferred().resolve().chain( null, function() { + ok( false, "chain should not be called on resolve" ); } ); - - strictEqual( test , "ABC" , "Test done callbacks order" ); - - deferred = jQuery._Deferred(); - - deferred.resolveWith( jQuery , [ document ] ).done( function( doc ) { - ok( this === jQuery && arguments.length === 1 && doc === document , "Test fire context & args" ); - }); - - // #8421 - deferred = jQuery._Deferred(); - deferred.resolveWith().done(function() { - ok( true, "Test resolveWith can be called with no argument" ); - }); }); -test("jQuery.Deferred()", function() { +test( "jQuery.Deferred.chain - deferred (done)", function() { - expect( 10 ); + expect(3); - jQuery.Deferred( function( defer ) { - strictEqual( this , defer , "Defer passed as this & first argument" ); - this.resolve( "done" ); - }).then( function( value ) { - strictEqual( value , "done" , "Passed function executed" ); + var defer = jQuery.Deferred(), + chained = defer.chain(function( a, b ) { + return jQuery.Deferred(function( defer ) { + defer.reject( a * b ); + }); + }), + value1, + value2, + value3; + + chained.fail(function( result ) { + value3 = result; }); - jQuery.Deferred().resolve().then( function() { - ok( true , "Success on resolve" ); - }, function() { - ok( false , "Error on resolve" ); + defer.done(function( a, b ) { + value1 = a; + value2 = b; }); - jQuery.Deferred().reject().then( function() { - ok( false , "Success on reject" ); - }, function() { - ok( true , "Error on reject" ); - }); + defer.resolve( 2, 3 ); - ( new jQuery.Deferred( function( defer ) { - strictEqual( this , defer , "Defer passed as this & first argument (new)" ); - this.resolve( "done" ); - }) ).then( function( value ) { - strictEqual( value , "done" , "Passed function executed (new)" ); - }); - - ( new jQuery.Deferred() ).resolve().then( function() { - ok( true , "Success on resolve (new)" ); - }, function() { - ok( false , "Error on resolve (new)" ); - }); - - ( new jQuery.Deferred() ).reject().then( function() { - ok( false , "Success on reject (new)" ); - }, function() { - ok( true , "Error on reject (new)" ); - }); - - var tmp = jQuery.Deferred(); - - strictEqual( tmp.promise() , tmp.promise() , "Test deferred always return same promise" ); - strictEqual( tmp.promise() , tmp.promise().promise() , "Test deferred's promise always return same promise as deferred" ); + strictEqual( value1, 2, "first resolve value ok" ); + strictEqual( value2, 3, "second resolve value ok" ); + strictEqual( value3, 6, "result of filter ok" ); }); -test("jQuery.when()", function() { +test( "jQuery.Deferred.chain - deferred (fail)", function() { + + expect(3); + + var defer = jQuery.Deferred(), + chained = defer.chain( null, function( a, b ) { + return jQuery.Deferred(function( defer ) { + defer.resolve( a * b ); + }); + } ), + value1, + value2, + value3; + + chained.done(function( result ) { + value3 = result; + }); + + defer.fail(function( a, b ) { + value1 = a; + value2 = b; + }); + + defer.reject( 2, 3 ); + + strictEqual( value1, 2, "first reject value ok" ); + strictEqual( value2, 3, "second reject value ok" ); + strictEqual( value3, 6, "result of filter ok" ); +}); + +test( "jQuery.when" , function() { expect( 23 ); @@ -166,57 +286,63 @@ test("jQuery.when()", function() { } , function( message , value ) { - ok( jQuery.isFunction( jQuery.when( value ).then( function( resolveValue ) { + ok( jQuery.isFunction( jQuery.when( value ).done(function( resolveValue ) { strictEqual( resolveValue , value , "Test the promise was resolved with " + message ); - } ).promise ) , "Test " + message + " triggers the creation of a new Promise" ); + }).promise ) , "Test " + message + " triggers the creation of a new Promise" ); } ); - ok( jQuery.isFunction( jQuery.when().then( function( resolveValue ) { + ok( jQuery.isFunction( jQuery.when().done(function( resolveValue ) { strictEqual( resolveValue , undefined , "Test the promise was resolved with no parameter" ); - } ).promise ) , "Test calling when with no parameter triggers the creation of a new Promise" ); + }).promise ) , "Test calling when with no parameter triggers the creation of a new Promise" ); var cache, i; for( i = 1 ; i < 4 ; i++ ) { jQuery.when( cache || jQuery.Deferred( function() { this.resolve( i ); - }) ).then( function( value ) { + }) ).done(function( value ) { strictEqual( value , 1 , "Function executed" + ( i > 1 ? " only once" : "" ) ); cache = value; - }, function() { - ok( false , "Fail called" ); }); } }); -test("jQuery.when() - joined", function() { +test("jQuery.when - joined", function() { - expect(8); + expect(25); - jQuery.when( 1, 2, 3 ).done( function( a, b, c ) { - strictEqual( a , 1 , "Test first param is first resolved value - non-observables" ); - strictEqual( b , 2 , "Test second param is second resolved value - non-observables" ); - strictEqual( c , 3 , "Test third param is third resolved value - non-observables" ); - }).fail( function() { - ok( false , "Test the created deferred was resolved - non-observables"); - }); + var deferreds = { + value: 1, + success: jQuery.Deferred().resolve( 1 ), + error: jQuery.Deferred().reject( 0 ), + futureSuccess: jQuery.Deferred(), + futureError: jQuery.Deferred() + }, + willSucceed = { + value: true, + success: true, + error: false, + futureSuccess: true, + futureError: false + }; - var successDeferred = jQuery.Deferred().resolve( 1 , 2 , 3 ), - errorDeferred = jQuery.Deferred().reject( "error" , "errorParam" ); - - jQuery.when( 1 , successDeferred , 3 ).done( function( a, b, c ) { - strictEqual( a , 1 , "Test first param is first resolved value - resolved observable" ); - same( b , [ 1 , 2 , 3 ] , "Test second param is second resolved value - resolved observable" ); - strictEqual( c , 3 , "Test third param is third resolved value - resolved observable" ); - }).fail( function() { - ok( false , "Test the created deferred was resolved - resolved observable"); - }); - - jQuery.when( 1 , errorDeferred , 3 ).done( function() { - ok( false , "Test the created deferred was rejected - rejected observable"); - }).fail( function( error , errorParam ) { - strictEqual( error , "error" , "Test first param is first rejected value - rejected observable" ); - strictEqual( errorParam , "errorParam" , "Test second param is second rejected value - rejected observable" ); - }); + jQuery.each( deferreds, function( id1, defer1 ) { + jQuery.each( deferreds, function( id2, defer2 ) { + var shouldResolve = willSucceed[ id1 ] && willSucceed[ id2 ], + expected = shouldResolve ? [ 1, 1 ] : [ 0, undefined ], + code = id1 + "/" + id2; + jQuery.when( defer1, defer2 ).done(function( a, b ) { + if ( shouldResolve ) { + same( [ a, b ], expected, code + " => resolve" ); + } + }).fail(function( a, b ) { + if ( !shouldResolve ) { + same( [ a, b ], expected, code + " => resolve" ); + } + }); + } ); + } ); + deferreds.futureSuccess.resolve( 1 ); + deferreds.futureError.reject( 0 ); });