Skip to content
Browse files

updating to the jQuery 1.8 api; v0.2.0 release

	* gave Callbacks tests their own file
	* updated _type implementation to conform to jQuery's
  • Loading branch information...
1 parent bac02fb commit fbee957859a9385fe3bc11dd6beaae2e44fcc0c9 Sam Breed committed
Showing with 575 additions and 459 deletions.
  1. +1 −1 grunt.js
  2. +16 −8 readme.md
  3. +262 −0 test/callbacks.js
  4. +72 −229 test/deferred.js
  5. +1 −0 test/index.html
  6. +223 −221 underscore.deferred.js
View
2 grunt.js
@@ -12,7 +12,7 @@ module.exports = function(grunt) {
},
concat: {
dist: {
- src: ['<banner:meta.banner>', '<file_strip_banner:lib/<%= pkg.name %>.js>'],
+ src: ['<banner:meta.banner>', '<file_strip_banner:<%= pkg.name %>.js>'],
dest: 'dist/<%= pkg.name %>.js'
}
},
View
24 readme.md
@@ -2,17 +2,18 @@
[![Build Status](https://secure.travis-ci.org/wookiehangover/underscore.deferred.png?branch=master)](http://travis-ci.org/wookiehangover/underscore.deferred)
-v0.1.5
+v0.2.0 (jQuery 1.8.0)
+
+This is a port of jQuery.Deferred as an Underscore mixin, but it can be
+used without any depencencies. It currently matches the Deferred specifications
+and implementation from jQuery 1.8.0, with all the associated helpers.
**Please note that as of 0.1.4 underscore.deferred will be ALL LOWERCASE on
npm.** The camelcasing was a mistake from the outset, please update your
`package.json` appropriately.
-This is a port of jQuery.Deferred as an Underscore mixin, but it can be
-used without any depencencies. It currently matches the Deferred specifications
-and implementation from jQuery 1.7.2, with all the associated helpers.
-## Deferred's are great, let's take them everywhere
+## Deferreds are great, let's take them everywhere
jQuery offers a robust, consistent and well documented API; this project aims
to make it portable. jQuery added a handful of helper methods to their
@@ -29,13 +30,20 @@ API. Here are some of the method implemented:
* [notifyWith](http://api.jquery.com/deferred.notifywith/)
* [pipe](http://api.jquery.com/deferred.pipe/)
* [promise](http://api.jquery.com/deferred.promise/)
-* [resolve](http://api.jquery.com/deferred.resolve/)
* [reject](http://api.jquery.com/deferred.reject/)
+* [rejectWith](http://api.jquery.com/deferred.rejectWith/)
+* [resolve](http://api.jquery.com/deferred.resolve/)
+* [resolveWith](http://api.jquery.com/deferred.resolve/)
* [state](http://api.jquery.com/deferred.notifywith/)
* [then](http://api.jquery.com/deferred.then/)
+* [when](http://api.jquery.com/jQuery.when/)
For the complete API documentation, look to the [jQuery Docs][jquery-docs].
+This project wouldn't exist if not for the the hard work and effort put
+into jQuery by its core team and contributors--thanks for making this
+possible!
+
## Usage
underscore.deferred works on the server and in the browser.
@@ -68,13 +76,13 @@ $ npm install
To build with [grunt](https://github.com/cowboy/grunt)
```
-$ node build
+$ grunt
```
To run headless Qunit tests (must have phantomjs in your path)
```
-$ node build qunit
+$ grunt qunit
```
## Contributors
View
262 test/callbacks.js
@@ -0,0 +1,262 @@
+module("callbacks");
+
+(function() {
+
+var output,
+ addToOutput = function( string ) {
+ return function() {
+ output += string;
+ };
+ },
+ outputA = addToOutput( "A" ),
+ outputB = addToOutput( "B" ),
+ outputC = addToOutput( "C" ),
+ tests = {
+ "": "XABC X XABCABCC X XBB X XABA X",
+ "once": "XABC X X X X X XABA X",
+ "memory": "XABC XABC XABCABCCC XA XBB XB XABA XC",
+ "unique": "XABC X XABCA X XBB X XAB X",
+ "stopOnFalse": "XABC X XABCABCC X XBB X XA X",
+ "once memory": "XABC XABC X XA X XA XABA XC",
+ "once unique": "XABC X X X X X XAB X",
+ "once stopOnFalse": "XABC X X X X X XA X",
+ "memory unique": "XABC XA XABCA XA XBB XB XAB XC",
+ "memory stopOnFalse": "XABC XABC XABCABCCC XA XBB XB XA X",
+ "unique stopOnFalse": "XABC X XABCA X XBB X XA X"
+ },
+ filters = {
+ "no filter": undefined,
+ "filter": function( fn ) {
+ return function() {
+ return fn.apply( this, arguments );
+ };
+ }
+ };
+
+ function showFlags( flags ) {
+ if ( typeof flags === "string" ) {
+ return '"' + flags + '"';
+ }
+ var output = [], key;
+ for ( key in flags ) {
+ output.push( '"' + key + '": ' + flags[ key ] );
+ }
+ return "{ " + output.join( ", " ) + " }";
+ }
+
+_.each( tests, function( resultString, strFlags ) {
+
+ var objectFlags = {};
+
+ _.each( strFlags.split( " " ), function( str ) {
+ if ( str.length ) {
+ objectFlags[ str ] = true;
+ }
+ });
+
+ _.each( filters, function( filter, filterLabel ) {
+
+ _.each( { "string": strFlags, "object": objectFlags }, function( flags, flagsTypes ) {
+
+ test( "_.Callbacks( " + showFlags( flags ) + " ) - " + filterLabel, function() {
+
+ expect( 20 );
+
+ // Give qunit a little breathing room
+ stop();
+ setTimeout( start, 0 );
+
+ var cblist,
+ results = resultString.split( /\s+/ );
+
+ // Basic binding and firing
+ output = "X";
+ cblist = _.Callbacks( flags );
+ cblist.add(function( str ) {
+ output += str;
+ });
+ cblist.fire( "A" );
+ strictEqual( output, "XA", "Basic binding and firing" );
+ strictEqual( cblist.fired(), true, ".fired() detects firing" );
+ output = "X";
+ cblist.disable();
+ cblist.add(function( str ) {
+ output += str;
+ });
+ strictEqual( output, "X", "Adding a callback after disabling" );
+ cblist.fire( "A" );
+ strictEqual( output, "X", "Firing after disabling" );
+
+ // Basic binding and firing (context, arguments)
+ output = "X";
+ cblist = _.Callbacks( flags );
+ cblist.add(function() {
+ equal( this, window, "Basic binding and firing (context)" );
+ output += Array.prototype.join.call( arguments, "" );
+ });
+ cblist.fireWith( window, [ "A", "B" ] );
+ strictEqual( output, "XAB", "Basic binding and firing (arguments)" );
+
+ // fireWith with no arguments
+ output = "";
+ cblist = _.Callbacks( flags );
+ cblist.add(function() {
+ equal( this, window, "fireWith with no arguments (context is window)" );
+ strictEqual( arguments.length, 0, "fireWith with no arguments (no arguments)" );
+ });
+ cblist.fireWith();
+
+ // Basic binding, removing and firing
+ output = "X";
+ cblist = _.Callbacks( flags );
+ cblist.add( outputA, outputB, outputC );
+ cblist.remove( outputB, outputC );
+ cblist.fire();
+ strictEqual( output, "XA", "Basic binding, removing and firing" );
+
+ // Empty
+ output = "X";
+ cblist = _.Callbacks( flags );
+ cblist.add( outputA );
+ cblist.add( outputB );
+ cblist.add( outputC );
+ cblist.empty();
+ cblist.fire();
+ strictEqual( output, "X", "Empty" );
+
+ // Locking
+ output = "X";
+ cblist = _.Callbacks( flags );
+ cblist.add( function( str ) {
+ output += str;
+ });
+ cblist.lock();
+ cblist.add( function( str ) {
+ output += str;
+ });
+ cblist.fire( "A" );
+ cblist.add( function( str ) {
+ output += str;
+ });
+ strictEqual( output, "X", "Lock early" );
+
+ // Ordering
+ output = "X";
+ cblist = _.Callbacks( flags );
+ cblist.add( function() {
+ cblist.add( outputC );
+ outputA();
+ }, outputB );
+ cblist.fire();
+ strictEqual( output, results.shift(), "Proper ordering" );
+
+ // Add and fire again
+ output = "X";
+ cblist.add( function() {
+ cblist.add( outputC );
+ outputA();
+ }, outputB );
+ strictEqual( output, results.shift(), "Add after fire" );
+
+ output = "X";
+ cblist.fire();
+ strictEqual( output, results.shift(), "Fire again" );
+
+ // Multiple fire
+ output = "X";
+ cblist = _.Callbacks( flags );
+ cblist.add( function( str ) {
+ output += str;
+ } );
+ cblist.fire( "A" );
+ strictEqual( output, "XA", "Multiple fire (first fire)" );
+ output = "X";
+ cblist.add( function( str ) {
+ output += str;
+ } );
+ strictEqual( output, results.shift(), "Multiple fire (first new callback)" );
+ output = "X";
+ cblist.fire( "B" );
+ strictEqual( output, results.shift(), "Multiple fire (second fire)" );
+ output = "X";
+ cblist.add( function( str ) {
+ output += str;
+ } );
+ strictEqual( output, results.shift(), "Multiple fire (second new callback)" );
+
+ // Return false
+ output = "X";
+ cblist = _.Callbacks( flags );
+ cblist.add( outputA, function() { return false; }, outputB );
+ cblist.add( outputA );
+ cblist.fire();
+ strictEqual( output, results.shift(), "Callback returning false" );
+
+ // Add another callback (to control lists with memory do not fire anymore)
+ output = "X";
+ cblist.add( outputC );
+ strictEqual( output, results.shift(), "Adding a callback after one returned false" );
+
+ });
+ });
+ });
+});
+
+})();
+
+test( "_.Callbacks( options ) - options are copied", function() {
+
+ expect( 1 );
+
+ var options = {
+ "unique": true
+ },
+ cb = _.Callbacks( options ),
+ count = 0,
+ fn = function() {
+ ok( !( count++ ), "called once" );
+ };
+ options["unique"] = false;
+ cb.add( fn, fn );
+ cb.fire();
+});
+
+test( "jQuery.Callbacks.fireWith - arguments are copied", function() {
+
+ expect( 1 );
+
+ var cb = _.Callbacks( "memory" ),
+ args = [ "hello" ];
+
+ cb.fireWith( null, args );
+ args[ 0 ] = "world";
+
+ cb.add(function( hello ) {
+ strictEqual( hello, "hello", "arguments are copied internally" );
+ });
+});
+
+test( "jQuery.Callbacks.remove - should remove all instances", function() {
+
+ expect( 1 );
+
+ var cb = _.Callbacks();
+
+ function fn() {
+ ok( false, "function wasn't removed" );
+ }
+
+ cb.add( fn, fn, function() {
+ ok( true, "end of test" );
+ }).remove( fn ).fire();
+});
+
+test( "_.Callbacks() - adding a string doesn't cause a stack overflow", function() {
+
+ expect( 1 );
+
+ _.Callbacks().add( "hello world" );
+
+ ok( true, "no stack overflow" );
+});
+
View
301 test/deferred.js
@@ -1,6 +1,8 @@
test("its should be part of Underscore", function() {
ok( _.VERSION );
- equal( typeof _.Deferred, 'function' );
+ ok( _.Deferred );
+ ok( _.when );
+ ok( _.Callbacks );
});
_.each( [ "", " - new operator" ], function( withNew ) {
@@ -11,36 +13,39 @@ _.each( [ "", " - new operator" ], function( withNew ) {
test("_.Deferred" + withNew, function() {
- expect( 22 );
+ expect( 21 );
- createDeferred().resolve().then( function() {
+ var defer = createDeferred();
+
+ strictEqual( defer.pipe, defer.then, "pipe is an alias of then" );
+
+ createDeferred().resolve().done(function() {
ok( true , "Success on resolve" );
- ok( this.isResolved(), "Deferred is resolved" );
strictEqual( this.state(), "resolved", "Deferred is resolved (state)" );
- }, function() {
+ }).fail(function() {
ok( false , "Error on resolve" );
- }).always( function() {
+ }).always(function() {
ok( true , "Always callback on resolve" );
});
- createDeferred().reject().then( function() {
+ createDeferred().reject().done(function() {
ok( false , "Success on reject" );
- }, function() {
+ }).fail(function() {
ok( true , "Error on reject" );
- ok( this.isRejected(), "Deferred is rejected" );
strictEqual( this.state(), "rejected", "Deferred is rejected (state)" );
- }).always( function() {
+ }).always(function() {
ok( true , "Always callback on reject" );
});
- createDeferred( function( defer ) {
+ createDeferred(function( defer ) {
ok( this === defer , "Defer passed as this & first argument" );
this.resolve( "done" );
- }).then( function( value ) {
+ }).done( function( value ) {
strictEqual( value , "done" , "Passed function executed" );
});
- _.each( "resolve reject".split( " " ), function( change ) {
+ _expandedEach = _.each;
+ _expandedEach( "resolve reject".split( " " ), function( change ) {
createDeferred( function( defer ) {
strictEqual( defer.state(), "pending", "pending after creation" );
var checked = 0;
@@ -63,24 +68,24 @@ _.each( [ "", " - new operator" ], function( withNew ) {
test( "_.Deferred - chainability", function() {
- var methods = "resolve reject notify resolveWith rejectWith notifyWith done fail progress then always".split( " " ),
- defer = _.Deferred();
+ var defer = _.Deferred();
- expect( methods.length );
+ expect( 10 );
- _.each( methods, function( method ) {
+ _expandedEach = _.each;
+ _expandedEach( "resolve reject notify resolveWith rejectWith notifyWith done fail progress always".split( " " ), function( method ) {
var object = { m: defer[ method ] };
strictEqual( object.m(), object, method + " is chainable" );
});
});
-test( "_.Deferred.pipe - filtering (done)", function() {
+test( "_.Deferred.then - filtering (done)", function() {
expect(4);
var defer = _.Deferred(),
- piped = defer.pipe(function( a, b ) {
+ piped = defer.then(function( a, b ) {
return a * b;
}),
value1,
@@ -102,21 +107,21 @@ test( "_.Deferred.pipe - filtering (done)", function() {
strictEqual( value2, 3, "second resolve value ok" );
strictEqual( value3, 6, "result of filter ok" );
- _.Deferred().reject().pipe(function() {
+ _.Deferred().reject().then(function() {
ok( false, "pipe should not be called on reject" );
});
- _.Deferred().resolve().pipe( _.noop ).done(function( value ) {
+ _.Deferred().resolve().then( function(){} ).done(function( value ) {
strictEqual( value, undefined, "pipe done callback can return undefined/null" );
});
});
-test( "_.Deferred.pipe - filtering (fail)", function() {
+test( "_.Deferred.then - filtering (fail)", function() {
expect(4);
var defer = _.Deferred(),
- piped = defer.pipe( null, function( a, b ) {
+ piped = defer.then( null, function( a, b ) {
return a * b;
} ),
value1,
@@ -138,7 +143,7 @@ test( "_.Deferred.pipe - filtering (fail)", function() {
strictEqual( value2, 3, "second reject value ok" );
strictEqual( value3, 6, "result of filter ok" );
- _.Deferred().resolve().pipe( null, function() {
+ _.Deferred().resolve().then( null, function() {
ok( false, "pipe should not be called on resolve" );
} );
@@ -147,12 +152,12 @@ test( "_.Deferred.pipe - filtering (fail)", function() {
});
});
-test( "_.Deferred.pipe - filtering (progress)", function() {
+test( "_.Deferred.then - filtering (progress)", function() {
expect(3);
var defer = _.Deferred(),
- piped = defer.pipe( null, null, function( a, b ) {
+ piped = defer.then( null, null, function( a, b ) {
return a * b;
} ),
value1,
@@ -175,12 +180,12 @@ test( "_.Deferred.pipe - filtering (progress)", function() {
strictEqual( value3, 6, "result of filter ok" );
});
-test( "_.Deferred.pipe - deferred (done)", function() {
+test( "_.Deferred.then - deferred (done)", function() {
expect(3);
var defer = _.Deferred(),
- piped = defer.pipe(function( a, b ) {
+ piped = defer.then(function( a, b ) {
return _.Deferred(function( defer ) {
defer.reject( a * b );
});
@@ -205,12 +210,12 @@ test( "_.Deferred.pipe - deferred (done)", function() {
strictEqual( value3, 6, "result of filter ok" );
});
-test( "_.Deferred.pipe - deferred (fail)", function() {
+test( "_.Deferred.then - deferred (fail)", function() {
expect(3);
var defer = _.Deferred(),
- piped = defer.pipe( null, function( a, b ) {
+ piped = defer.then( null, function( a, b ) {
return _.Deferred(function( defer ) {
defer.resolve( a * b );
});
@@ -235,12 +240,12 @@ test( "_.Deferred.pipe - deferred (fail)", function() {
strictEqual( value3, 6, "result of filter ok" );
});
-test( "_.Deferred.pipe - deferred (progress)", function() {
+test( "_.Deferred.then - deferred (progress)", function() {
expect(3);
var defer = _.Deferred(),
- piped = defer.pipe( null, null, function( a, b ) {
+ piped = defer.then( null, null, function( a, b ) {
return _.Deferred(function( defer ) {
defer.resolve( a * b );
});
@@ -265,13 +270,13 @@ test( "_.Deferred.pipe - deferred (progress)", function() {
strictEqual( value3, 6, "result of filter ok" );
});
-test( "_.Deferred.pipe - context", function() {
+test( "_.Deferred.then - context", function() {
expect(4);
var context = {};
- _.Deferred().resolveWith( context, [ 2 ] ).pipe(function( value ) {
+ _.Deferred().resolveWith( context, [ 2 ] ).then(function( value ) {
return value * 3;
}).done(function( value ) {
strictEqual( this, context, "custom context correctly propagated" );
@@ -279,7 +284,7 @@ test( "_.Deferred.pipe - context", function() {
});
var defer = _.Deferred(),
- piped = defer.pipe(function( value ) {
+ piped = defer.then(function( value ) {
return value * 3;
});
@@ -293,7 +298,7 @@ test( "_.Deferred.pipe - context", function() {
test( "_.when" , function() {
- expect( 23 );
+ expect( 34 );
// Some other objects
_.each( {
@@ -308,33 +313,46 @@ test( "_.when" , function() {
"undefined": undefined,
"a plain object": {}
- } , function( value, message ) {
+ } , function( value, message ) {
ok( _.isFunction( _.when( value ).done(function( resolveValue ) {
+ strictEqual( this, window, "Context is the global object with " + message );
strictEqual( resolveValue , value , "Test the promise was resolved with " + message );
}).promise ) , "Test " + message + " triggers the creation of a new Promise" );
} );
ok( _.isFunction( _.when().done(function( resolveValue ) {
- strictEqual( resolveValue , undefined , "Test the promise was resolved with no parameter" );
+ strictEqual( this, window, "Test the promise was resolved with window as its context" );
+ 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" );
- var cache, i;
+ var context = {};
+
+ _.when( _.Deferred().resolveWith( context ) ).done(function() {
+ strictEqual( this, context, "when( promise ) propagates context" );
+ });
+
+ var cache;
+
+ _.each([ 1, 2, 3 ], function(i, k) {
- for( i = 1 ; i < 4 ; i++ ) {
_.when( cache || _.Deferred( function() {
- this.resolve( i );
- }) ).done(function( value ) {
- strictEqual( value , 1 , "Function executed" + ( i > 1 ? " only once" : "" ) );
+ this.resolve( i );
+ })
+ ).done(function( value ) {
+
+ strictEqual( value, 1 , "Function executed" + ( i > 1 ? " only once" : "" ) );
cache = value;
});
- }
+
+ });
+
});
test("_.when - joined", function() {
- expect(53);
+ expect( 119 );
var deferreds = {
value: 1,
@@ -366,11 +384,15 @@ test("_.when - joined", function() {
shouldNotify = willNotify[ id1 ] || willNotify[ id2 ],
expected = shouldResolve ? [ 1, 1 ] : [ 0, undefined ],
expectedNotify = shouldNotify && [ willNotify[ id1 ], willNotify[ id2 ] ],
- code = id1 + "/" + id2;
+ code = id1 + "/" + id2,
+ context1 = defer1 && _.isFunction( defer1.promise ) ? defer1 : undefined,
+ context2 = defer2 && _.isFunction( defer2.promise ) ? defer2 : undefined;
- var promise = _.when( defer1, defer2 ).done(function( a, b ) {
+ _.when( defer1, defer2 ).done(function( a, b ) {
if ( shouldResolve ) {
deepEqual( [ a, b ], expected, code + " => resolve" );
+ strictEqual( this[ 0 ], context1, code + " => first context OK" );
+ strictEqual( this[ 1 ], context2, code + " => second context OK" );
} else {
ok( false , code + " => resolve" );
}
@@ -380,194 +402,15 @@ test("_.when - joined", function() {
} else {
ok( false , code + " => reject" );
}
- }).progress(function progress( a, b ) {
+ }).progress(function( a, b ) {
deepEqual( [ a, b ], expectedNotify, code + " => progress" );
+ strictEqual( this[ 0 ], expectedNotify[ 0 ] ? context1 : undefined, code + " => first context OK" );
+ strictEqual( this[ 1 ], expectedNotify[ 1 ] ? context2 : undefined, code + " => second context OK" );
});
} );
} );
deferreds.futureSuccess.resolve( 1 );
deferreds.futureError.reject( 0 );
-});
-
-(function() {
-
-var output,
- addToOutput = function( string ) {
- return function() {
- output += string;
- };
- },
- outputA = addToOutput( "A" ),
- outputB = addToOutput( "B" ),
- outputC = addToOutput( "C" ),
- tests = {
- "": "XABC X XABCABCC X XBB X XABA X",
- "once": "XABC X X X X X XABA X",
- "memory": "XABC XABC XABCABCCC XA XBB XB XABA XC",
- "unique": "XABC X XABCA X XBB X XAB X",
- "stopOnFalse": "XABC X XABCABCC X XBB X XA X",
- "once memory": "XABC XABC X XA X XA XABA XC",
- "once unique": "XABC X X X X X XAB X",
- "once stopOnFalse": "XABC X X X X X XA X",
- "memory unique": "XABC XA XABCA XA XBB XB XAB XC",
- "memory stopOnFalse": "XABC XABC XABCABCCC XA XBB XB XA X",
- "unique stopOnFalse": "XABC X XABCA X XBB X XA X"
- },
- filters = {
- "no filter": undefined,
- "filter": function( fn ) {
- return function() {
- return fn.apply( this, arguments );
- };
- }
- };
-
-_.each( tests, function( resultString, flags ) {
-
- _.each( filters, function( filter, filterLabel ) {
-
- test( "_.Callbacks( \"" + flags + "\" ) - " + filterLabel, function() {
-
- expect( 20 );
-
- // Give qunit a little breathing room
- stop();
- setTimeout( start, 0 );
-
- var cblist;
- results = resultString.split( /\s+/ );
-
- // Basic binding and firing
- output = "X";
- cblist = _.Callbacks( flags );
- cblist.add(function( str ) {
- output += str;
- });
- cblist.fire( "A" );
- strictEqual( output, "XA", "Basic binding and firing" );
- strictEqual( cblist.fired(), true, ".fired() detects firing" );
- output = "X";
- cblist.disable();
- cblist.add(function( str ) {
- output += str;
- });
- strictEqual( output, "X", "Adding a callback after disabling" );
- cblist.fire( "A" );
- strictEqual( output, "X", "Firing after disabling" );
-
- // Basic binding and firing (context, arguments)
- output = "X";
- cblist = _.Callbacks( flags );
- cblist.add(function() {
- equal( this, window, "Basic binding and firing (context)" );
- output += Array.prototype.join.call( arguments, "" );
- });
- cblist.fireWith( window, [ "A", "B" ] );
- strictEqual( output, "XAB", "Basic binding and firing (arguments)" );
-
- // fireWith with no arguments
- output = "";
- cblist = _.Callbacks( flags );
- cblist.add(function() {
- equal( this, window, "fireWith with no arguments (context is window)" );
- strictEqual( arguments.length, 0, "fireWith with no arguments (no arguments)" );
- });
- cblist.fireWith();
-
- // Basic binding, removing and firing
- output = "X";
- cblist = _.Callbacks( flags );
- cblist.add( outputA, outputB, outputC );
- cblist.remove( outputB, outputC );
- cblist.fire();
- strictEqual( output, "XA", "Basic binding, removing and firing" );
-
- // Empty
- output = "X";
- cblist = _.Callbacks( flags );
- cblist.add( outputA );
- cblist.add( outputB );
- cblist.add( outputC );
- cblist.empty();
- cblist.fire();
- strictEqual( output, "X", "Empty" );
-
- // Locking
- output = "X";
- cblist = _.Callbacks( flags );
- cblist.add( function( str ) {
- output += str;
- });
- cblist.lock();
- cblist.add( function( str ) {
- output += str;
- });
- cblist.fire( "A" );
- cblist.add( function( str ) {
- output += str;
- });
- strictEqual( output, "X", "Lock early" );
-
- // Ordering
- output = "X";
- cblist = _.Callbacks( flags );
- cblist.add( function() {
- cblist.add( outputC );
- outputA();
- }, outputB );
- cblist.fire();
- strictEqual( output, results.shift(), "Proper ordering" );
-
- // Add and fire again
- output = "X";
- cblist.add( function() {
- cblist.add( outputC );
- outputA();
- }, outputB );
- strictEqual( output, results.shift(), "Add after fire" );
-
- output = "X";
- cblist.fire();
- strictEqual( output, results.shift(), "Fire again" );
-
- // Multiple fire
- output = "X";
- cblist = _.Callbacks( flags );
- cblist.add( function( str ) {
- output += str;
- } );
- cblist.fire( "A" );
- strictEqual( output, "XA", "Multiple fire (first fire)" );
- output = "X";
- cblist.add( function( str ) {
- output += str;
- } );
- strictEqual( output, results.shift(), "Multiple fire (first new callback)" );
- output = "X";
- cblist.fire( "B" );
- strictEqual( output, results.shift(), "Multiple fire (second fire)" );
- output = "X";
- cblist.add( function( str ) {
- output += str;
- } );
- strictEqual( output, results.shift(), "Multiple fire (second new callback)" );
-
- // Return false
- output = "X";
- cblist = _.Callbacks( flags );
- cblist.add( outputA, function() { return false; }, outputB );
- cblist.add( outputA );
- cblist.fire();
- strictEqual( output, results.shift(), "Callback returning false" );
-
- // Add another callback (to control lists with memory do not fire anymore)
- output = "X";
- cblist.add( outputC );
- strictEqual( output, results.shift(), "Adding a callback after one returned false" );
-
- });
- });
});
-})();
View
1 test/index.html
@@ -14,6 +14,7 @@
<!-- test runner files -->
<script src="qunit/qunit.js"></script>
<script src="deferred.js"></script>
+ <script src="callbacks.js"></script>
View
444 underscore.deferred.js
@@ -10,6 +10,7 @@
hasOwn = OP.hasOwnProperty,
toString = OP.toString,
forEach = AP.forEach,
+ indexOf = AP.indexOf,
slice = AP.slice;
var _each = function( obj, iterator, context ) {
@@ -57,12 +58,40 @@
return obj;
};
+ // $.inArray
+ var _inArray = function( elem, arr, i ) {
+ var len;
+
+ if ( arr ) {
+ if ( indexOf ) {
+ return indexOf.call( arr, elem, i );
+ }
+
+ len = arr.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in arr && arr[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ };
+
// And some jQuery specific helpers
- var class2type = { "[object Array]": "array", "[object Function]": "function" };
+ var class2type = {};
+
+ // Populate the class2type map
+ _each("Boolean Number String Function Array Date RegExp Object".split(" "), function(name, i) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+ });
var _type = function( obj ) {
- return !obj ?
+ return obj == null ?
String( obj ) :
class2type[ toString.call(obj) ] || "object";
};
@@ -72,30 +101,27 @@
// Internal Deferred namespace
var _d = {};
-
- var flagsCache = {};
- // Convert String-formatted flags into Object-formatted ones and store in cache
- function createFlags( flags ) {
- var object = flagsCache[ flags ] = {},
- i, length;
- flags = flags.split( /\s+/ );
- for ( i = 0, length = flags.length; i < length; i++ ) {
- object[ flags[i] ] = true;
- }
- return object;
+ // String to Object options format cache
+ var optionsCache = {};
+
+ // Convert String-formatted options into Object-formatted ones and store in cache
+ function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ _each( options.split( /\s+/ ), function( flag ) {
+ object[ flag ] = true;
+ });
+ return object;
}
- _d.Callbacks = function( flags ) {
+ _d.Callbacks = function( options ) {
- // Convert flags from String-formatted to Object-formatted
+ // Convert options from String-formatted to Object-formatted if needed
// (we check in cache first)
- flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ _extend( {}, options );
- var // Actual callback list
- list = [],
- // Stack of fire calls for repeatable lists
- stack = [],
- // Last fire value (for non-forgettable lists)
+ var // Last fire value (for non-forgettable lists)
memory,
// Flag to know if list was already fired
fired,
@@ -107,53 +133,34 @@
firingLength,
// Index of currently firing callback (modified by remove if needed)
firingIndex,
- // Add one or several callbacks to the list
- add = function( args ) {
- var i,
- length,
- elem,
- type,
- actual;
- for ( i = 0, length = args.length; i < length; i++ ) {
- elem = args[ i ];
- type = _type( elem );
- if ( type === "array" ) {
- // Inspect recursively
- add( elem );
- } else if ( type === "function" ) {
- // Add if not in unique mode and callback is not in
- if ( !flags.unique || !self.has( elem ) ) {
- list.push( elem );
- }
- }
- }
- },
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
// Fire callbacks
- fire = function( context, args ) {
- args = args || [];
- memory = !flags.memory || [ context, args ];
+ fire = function( data ) {
+ memory = options.memory && data;
fired = true;
- firing = true;
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
+ firing = true;
for ( ; list && firingIndex < firingLength; firingIndex++ ) {
- if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
- memory = true; // Mark as halted
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
break;
}
}
firing = false;
if ( list ) {
- if ( !flags.once ) {
- if ( stack && stack.length ) {
- memory = stack.shift();
- self.fireWith( memory[ 0 ], memory[ 1 ] );
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
}
- } else if ( memory === true ) {
- self.disable();
- } else {
+ } else if ( memory ) {
list = [];
+ } else {
+ self.disable();
}
}
},
@@ -162,18 +169,28 @@
// Add a callback or a collection of callbacks to the list
add: function() {
if ( list ) {
- var length = list.length;
- add( arguments );
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ _each( args, function( arg ) {
+ var type = _type( arg );
+ if ( type === "function" && ( !options.unique || !self.has( arg ) ) ) {
+ list.push( arg );
+ } else if ( arg && arg.length && type !== "string" ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
// Do we need to add the callbacks to the
// current firing batch?
if ( firing ) {
firingLength = list.length;
// With memory, if we're not firing then
- // we should call right away, unless previous
- // firing was halted (stopOnFalse)
- } else if ( memory && memory !== true ) {
- firingStart = length;
- fire( memory[ 0 ], memory[ 1 ] );
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
}
}
return this;
@@ -181,46 +198,27 @@
// Remove a callback from the list
remove: function() {
if ( list ) {
- var args = arguments,
- argIndex = 0,
- argLength = args.length;
- for ( ; argIndex < argLength ; argIndex++ ) {
- for ( var i = 0; i < list.length; i++ ) {
- if ( args[ argIndex ] === list[ i ] ) {
- // Handle firingIndex and firingLength
- if ( firing ) {
- if ( i <= firingLength ) {
- firingLength--;
- if ( i <= firingIndex ) {
- firingIndex--;
- }
- }
+ _each( arguments, function( arg ) {
+ var index;
+ while( ( index = _inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
}
- // Remove the element
- list.splice( i--, 1 );
- // If we have some unicity property then
- // we only need to do this once
- if ( flags.unique ) {
- break;
+ if ( index <= firingIndex ) {
+ firingIndex--;
}
}
}
- }
+ });
}
return this;
},
// Control if a given callback is in the list
has: function( fn ) {
- if ( list ) {
- var i = 0,
- length = list.length;
- for ( ; i < length; i++ ) {
- if ( fn === list[ i ] ) {
- return true;
- }
- }
- }
- return false;
+ return _inArray( fn, list ) > -1;
},
// Remove all callbacks from the list
empty: function() {
@@ -239,7 +237,7 @@
// Lock the list in its current state
lock: function() {
stack = undefined;
- if ( !memory || memory === true ) {
+ if ( !memory ) {
self.disable();
}
return this;
@@ -250,13 +248,13 @@
},
// Call all callbacks with the given context and arguments
fireWith: function( context, args ) {
- if ( stack ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( list && ( !fired || stack ) ) {
if ( firing ) {
- if ( !flags.once ) {
- stack.push( [ context, args ] );
- }
- } else if ( !( flags.once && memory ) ) {
- fire( context, args );
+ stack.push( args );
+ } else {
+ fire( args );
}
}
return this;
@@ -276,140 +274,144 @@
};
_d.Deferred = function( func ) {
- var doneList = _d.Callbacks( "once memory" ),
- failList = _d.Callbacks( "once memory" ),
- progressList = _d.Callbacks( "memory" ),
- state = "pending",
- lists = {
- resolve: doneList,
- reject: failList,
- notify: progressList
+
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", _d.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", _d.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", _d.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
},
- promise = {
- done: doneList.add,
- fail: failList.add,
- progress: progressList.add,
-
- state: function() {
- return state;
- },
-
- // Deprecated
- isResolved: doneList.fired,
- isRejected: failList.fired,
-
- then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
- deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
- return this;
- },
- always: function() {
- deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
- return this;
- },
- pipe: function( fnDone, fnFail, fnProgress ) {
- return _d.Deferred(function( newDefer ) {
- _each( {
- done: [ fnDone, "resolve" ],
- fail: [ fnFail, "reject" ],
- progress: [ fnProgress, "notify" ]
- }, function( data, handler ) {
- var fn = data[ 0 ],
- action = data[ 1 ],
- returned;
- if ( _isFunction( fn ) ) {
- deferred[ handler ](function() {
- returned = fn.apply( this, arguments );
- if ( returned && _isFunction( returned.promise ) ) {
- returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
- } else {
- newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ 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 ) {
- if ( !obj ) {
- obj = promise;
- } else {
- for ( var key in promise ) {
- obj[ key ] = promise[ key ];
- }
- }
- return obj;
- }
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
},
- deferred = promise.promise({}),
- key;
-
- for ( key in lists ) {
- deferred[ key ] = lists[ key ].fire;
- deferred[ key + "With" ] = lists[ key ].fireWith;
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+ return _d.Deferred(function( newDefer ) {
+ _each( tuples, function( tuple, i ) {
+ var action = tuple[ 0 ],
+ fn = fns[ i ];
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ]( _isFunction( fn ) ?
+ function() {
+ var returned = fn.apply( this, arguments );
+ if ( returned && _isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ } :
+ newDefer[ action ]
+ );
+ });
+ fns = null;
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return typeof obj === "object" ? _extend( obj, promise ) : promise;
}
+ },
+ deferred = {};
- // Handle state
- deferred.done( function() {
- state = "resolved";
- }, failList.disable, progressList.lock ).fail( function() {
- state = "rejected";
- }, doneList.disable, progressList.lock );
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
- // Call given func if any
- if ( func ) {
- func.call( deferred, deferred );
- }
+ // Add list-specific methods
+ _each( tuples, function( tuple, i ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // deferred[ resolve | reject | notify ] = list.fire
+ deferred[ tuple[0] ] = list.fire;
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
- // All done!
- return deferred;
- };
+ // All done!
+ return deferred;
+ };
// Deferred helper
- _d.when = function( firstParam ) {
- var args = slice.call( arguments, 0 ),
- i = 0,
- length = args.length,
- pValues = new Array( length ),
- count = length,
- pCount = length,
- deferred = length <= 1 && firstParam && _isFunction( firstParam.promise ) ?
- firstParam :
- _d.Deferred(),
- promise = deferred.promise();
- function resolveFunc( i ) {
+ _d.when = function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && _isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : _d.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
return function( value ) {
- args[ i ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value;
- if ( !( --count ) ) {
- deferred.resolveWith( deferred, args );
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+ if( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
}
};
- }
- function progressFunc( i ) {
- return function( value ) {
- pValues[ i ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value;
- deferred.notifyWith( promise, pValues );
- };
- }
- if ( length > 1 ) {
- for ( ; i < length; i++ ) {
- if ( args[ i ] && args[ i ].promise && _isFunction( args[ i ].promise ) ) {
- args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
- } else {
- --count;
- }
- }
- if ( !count ) {
- deferred.resolveWith( deferred, args );
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && _isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
}
- } else if ( deferred !== firstParam ) {
- deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
}
- return promise;
- };
+ }
+
+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ };
// Try exporting as a Common.js Module
if ( typeof module !== "undefined" && module.exports ) {

0 comments on commit fbee957

Please sign in to comment.
Something went wrong with that request. Please try again.