From 4e975430510f443ef76a90d077bc8956fb8b8cc0 Mon Sep 17 00:00:00 2001 From: jaubourg Date: Mon, 31 Jan 2011 14:36:59 +0100 Subject: [PATCH] Adds an invert method to promises that returns a "inverted" promise that is resolved when the underlying deferred is rejected and rejected when the underlying deferred is resolved. --- src/core.js | 42 ++++++++++++++++++++++++++++++++++-------- test/unit/core.js | 50 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 75 insertions(+), 17 deletions(-) diff --git a/src/core.js b/src/core.js index faf205c..915ac6b 100644 --- a/src/core.js +++ b/src/core.js @@ -56,8 +56,16 @@ var jQuery = function( selector, context ) { // The deferred used on DOM ready readyList, - // Promise methods - promiseMethods = "then done fail isResolved isRejected promise".split( " " ), + // Promise methods (with equivalent for invert) + promiseMethods = { + then: 0, // will be overwritten for invert + done: "fail", + fail: "done", + isResolved: "isRejected", + isRejected: "isResolved", + promise: "invert", + invert: "promise" + }, // The ready event handler DOMContentLoaded, @@ -879,8 +887,9 @@ jQuery.extend({ Deferred: function( func ) { var deferred = jQuery._Deferred(), failDeferred = jQuery._Deferred(), - promise; - // Add errorDeferred methods, then and promise + promise, + invert; + // Add errorDeferred methods, then, promise and invert jQuery.extend( deferred, { then: function( doneCallbacks, failCallbacks ) { deferred.done( doneCallbacks ).fail( failCallbacks ); @@ -892,17 +901,34 @@ jQuery.extend({ isRejected: failDeferred.isResolved, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object - promise: function( obj , i /* internal */ ) { + promise: function( obj ) { if ( obj == null ) { if ( promise ) { return promise; } promise = obj = {}; } - i = promiseMethods.length; - while( i-- ) { - obj[ promiseMethods[ i ] ] = deferred[ promiseMethods[ i ] ]; + for( var methodName in promiseMethods ) { + obj[ methodName ] = deferred[ methodName ]; + } + return obj; + }, + // Get the invert promise for this deferred + // If obj is provided, the invert promise aspect is added to the object + invert: function( obj ) { + if ( obj == null ) { + if ( invert ) { + return invert; + } + invert = obj = {}; + } + for( var methodName in promiseMethods ) { + obj[ methodName ] = promiseMethods[ methodName ] && deferred[ promiseMethods[methodName] ]; } + obj.then = invert.then || function( doneCallbacks, failCallbacks ) { + deferred.done( failCallbacks ).fail( doneCallbacks ); + return this; + }; return obj; } } ); diff --git a/test/unit/core.js b/test/unit/core.js index 2c9a5ea..a7b3b48 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -554,8 +554,8 @@ test("toArray()", function() { expect(1); same( jQuery("p").toArray(), q("firstp","ap","sndp","en","sap","first"), - "Convert jQuery object to an Array" ) -}) + "Convert jQuery object to an Array" ); +}); test("get(Number)", function() { expect(2); @@ -567,7 +567,7 @@ test("get(-Number)",function() { expect(2); equals( jQuery("p").get(-1), document.getElementById("first"), "Get a single element with negative index" ); strictEqual( jQuery("#firstp").get(-2), undefined, "Try get with index negative index larger then elements count" ); -}) +}); test("each(Function)", function() { expect(1); @@ -1009,7 +1009,7 @@ test("jQuery._Deferred()", function() { test("jQuery.Deferred()", function() { - expect( 10 ); + expect( 20 ); jQuery.Deferred( function( defer ) { strictEqual( this , defer , "Defer passed as this & first argument" ); @@ -1049,10 +1049,26 @@ test("jQuery.Deferred()", function() { ok( true , "Error on reject (new)" ); }); + strictEqual( jQuery.Deferred().resolve( "test" ).invert().then(null,function(value) { + strictEqual( value, "test", "Resolved deferred => then fail callback called" ); + }).fail(function( value ) { + strictEqual( value, "test", "Resolved deferred => fail callback called" ); + }).isRejected(), true, "Invert promise is rejected when deferred is resolved" ); + + strictEqual( jQuery.Deferred().reject( "test" ).invert().then(function(value) { + strictEqual( value, "test", "Rejected deferred => then done callback called" ); + }).done(function( value ) { + strictEqual( value, "test", "Rejected deferred => done callback called" ); + }).isResolved(), true, "Invert promise is resolved when deferred is rejected" ); + var tmp = jQuery.Deferred(); strictEqual( tmp.promise() , tmp.promise() , "Test deferred always return same promise" ); + strictEqual( tmp.invert() , tmp.invert() , "Test deferred always return same invert" ); strictEqual( tmp.promise() , tmp.promise().promise() , "Test deferred's promise always return same promise as deferred" ); + strictEqual( tmp.promise() , tmp.invert().invert() , "Test deferred's promise is the same as double invert" ); + strictEqual( tmp.invert() , tmp.invert().promise() , "Test deferred's invert always return same invert as deferred as a promise" ); + strictEqual( tmp.invert() , tmp.promise().invert() , "Test deferred's promise always return same invert as deferred" ); }); test("jQuery.when()", function() { @@ -1100,7 +1116,7 @@ test("jQuery.when()", function() { test("jQuery.when() - joined", function() { - expect(8); + expect(14); jQuery.when( 1, 2, 3 ).done( function( a, b, c ) { strictEqual( a , 1 , "Test first param is first resolved value - non-observables" ); @@ -1121,12 +1137,28 @@ test("jQuery.when() - joined", function() { ok( false , "Test the created deferred was resolved - resolved observable"); }); + jQuery.when( 1 , successDeferred.invert() , 3 ).fail( function( a, b, c ) { + strictEqual( a , 1 , "Test first param is first rejected value - resolved observable inverted" ); + same( b , 2 , "Test second param is second rejected value - resolved observable inverted" ); + strictEqual( c , 3 , "Test third param is third rejected value - resolved observable inverted" ); + }).done( function() { + ok( false , "Test the inverted deferred was rejected - resolved observable inverted"); + }); + 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.when( 1 , errorDeferred.invert() , 3 ).fail( function() { + ok( false , "Test the inverted deferred was resolved - rejected observable inverted"); + }).done( function( a , b , c ) { + strictEqual( a , 1 , "Test first param is first resolved value - rejected observable inverted" ); + same( b , [ "error", "errorParam" ] , "Test second param is second resolved value - rejected observable inverted" ); + strictEqual( c , 3 , "Test third param is third resolved value - rejected observable inverted" ); + }); }); test("jQuery.sub() - Static Methods", function(){ @@ -1143,16 +1175,16 @@ test("jQuery.sub() - Static Methods", function(){ } }); Subclass.fn.extend({subClassMethod: function() { return this;}}); - + //Test Simple Subclass ok(Subclass.topLevelMethod() === false, 'Subclass.topLevelMethod thought debug was true'); ok(Subclass.config.locale == 'en_US', Subclass.config.locale + ' is wrong!'); same(Subclass.config.test, undefined, 'Subclass.config.test is set incorrectly'); equal(jQuery.ajax, Subclass.ajax, 'The subclass failed to get all top level methods'); - + //Create a SubSubclass var SubSubclass = Subclass.sub(); - + //Make Sure the SubSubclass inherited properly ok(SubSubclass.topLevelMethod() === false, 'SubSubclass.topLevelMethod thought debug was true'); ok(SubSubclass.config.locale == 'en_US', SubSubclass.config.locale + ' is wrong!'); @@ -1169,7 +1201,7 @@ test("jQuery.sub() - Static Methods", function(){ ok(SubSubclass.config.locale == 'es_MX', SubSubclass.config.locale + ' is wrong!'); ok(SubSubclass.config.test == 'worked', 'SubSubclass.config.test is set incorrectly'); notEqual(jQuery.ajax, SubSubclass.ajax, 'The subsubclass failed to get all top level methods'); - + //This shows that the modifications to the SubSubClass did not bubble back up to it's superclass ok(Subclass.topLevelMethod() === false, 'Subclass.topLevelMethod thought debug was true'); ok(Subclass.config.locale == 'en_US', Subclass.config.locale + ' is wrong!'); -- 1.7.10.4