| <!DOCTYPE html> |
| <html> |
| <!-- |
| Copyright 2009 The Closure Library Authors. All Rights Reserved. |
| Author: brenneman@google.com (Shawn Brenneman) |
| --> |
| <head> |
| <title>Closure Unit Tests - goog.async.DeferredList</title> |
| <script src="../../../../../closure/goog/base.js"></script> |
| <script> |
| goog.require('goog.array'); |
| goog.require('goog.async.Deferred'); |
| goog.require('goog.async.DeferredList'); |
| goog.require('goog.testing.jsunit'); |
| </script> |
| </head> |
| <body> |
| <script> |
| |
| var Deferred = goog.async.Deferred; |
| var DeferredList = goog.async.DeferredList; |
| |
| |
| // Re-throw (after a timeout) any errors not handled in an errback. |
| Deferred.STRICT_ERRORS = true; |
| |
| |
| /** |
| * A list of unhandled errors. |
| * @type {Array.<Error>} |
| */ |
| var storedErrors = []; |
| |
| |
| /** |
| * Adds a catch-all error handler to deferred objects. Unhandled errors that |
| * reach the catch-all will be rethrown during tearDown. |
| * |
| * @param {...Deferred} var_args A list of deferred objects. |
| */ |
| function addCatchAll(var_args) { |
| for (var i = 0, d; d = arguments[i]; i++) { |
| d.addErrback(function(res) { |
| storedErrors.push(res); |
| }); |
| } |
| } |
| |
| |
| /** |
| * Checks storedErrors for unhandled errors. If found, the error is rethrown. |
| */ |
| function checkCatchAll() { |
| var err = storedErrors.shift(); |
| goog.array.clear(storedErrors); |
| |
| if (err) { |
| throw err; |
| } |
| } |
| |
| |
| function tearDown() { |
| checkCatchAll(); |
| } |
| |
| |
| function neverHappen(res) { |
| fail('This should not happen'); |
| } |
| |
| |
| function testNoInputs() { |
| var count = 0; |
| var d = new DeferredList([]); |
| |
| d.addCallback(function(res) { |
| assertArrayEquals([], res); |
| count++; |
| }); |
| addCatchAll(d); |
| |
| assertEquals('An empty DeferredList should fire immediately with an empty ' + |
| 'list of results.', |
| 1, count); |
| } |
| |
| |
| function testNoInputsAndFireOnOneCallback() { |
| var count = 0; |
| var d = new DeferredList([], true); |
| |
| d.addCallback(function(res) { |
| assertArrayEquals([], res); |
| count++; |
| }); |
| addCatchAll(d); |
| |
| assertEquals('An empty DeferredList with opt_fireOnOneCallback set should ' + |
| 'not fire unless callback is invoked explicitly.', |
| 0, count); |
| |
| d.callback([]); |
| assertEquals('Calling callback explicitly should still fire.', 1, count); |
| } |
| |
| |
| function testDeferredList() { |
| var count = 0; |
| var results; |
| |
| var a = new Deferred(); |
| var b = new Deferred(); |
| var c = new Deferred(); |
| |
| var dl = new DeferredList([a, b, c]); |
| |
| dl.addCallback(function(res) { |
| assertEquals('Expected 3 Deferred results.', 3, res.length); |
| |
| assertTrue('Deferred a should return success.', res[0][0]); |
| assertFalse('Deferred b should return failure.', res[1][0]); |
| assertTrue('Deferred c should return success.', res[2][0]); |
| |
| assertEquals('Unexpected return value for a.', 'A', res[0][1]); |
| assertEquals('Unexpected return value for c.', 'C', res[2][1]); |
| |
| assertEquals('B', res[1][1]); |
| |
| count++; |
| }); |
| |
| addCatchAll(dl); |
| |
| c.callback('C'); |
| assertEquals(0, count); |
| |
| b.errback('B'); |
| assertEquals(0, count); |
| |
| a.callback('A'); |
| |
| checkCatchAll(); |
| assertEquals('DeferredList should fire on last call or errback.', 1, count); |
| } |
| |
| |
| function testFireOnFirstCallback() { |
| var a = new Deferred(); |
| var b = new Deferred(); |
| var c = new Deferred(); |
| |
| var dl = new DeferredList([a, b, c], true); |
| |
| dl.addCallback(function(res) { |
| assertEquals('Should be the deferred index in this mode.', 1, res[0]); |
| assertEquals('B', res[1]); |
| }); |
| dl.addErrback(neverHappen); |
| |
| addCatchAll(dl); |
| |
| a.errback('A'); |
| b.callback('B'); |
| |
| // Shouldn't cause any more callbacks on the DeferredList. |
| c.callback('C'); |
| } |
| |
| |
| function testFireOnFirstErrback() { |
| var a = new Deferred(); |
| var b = new Deferred(); |
| var c = new Deferred(); |
| |
| var dl = new DeferredList([a, b, c], false, true); |
| |
| dl.addCallback(neverHappen); |
| dl.addErrback(function(res) { |
| assertEquals('A', res); |
| |
| // Return a non-error value to break out of the errback path. |
| return null; |
| }); |
| addCatchAll(dl); |
| |
| b.callback('B'); |
| a.errback('A'); |
| |
| assertTrue(c.hasFired()); |
| c.addErrback(function(res) { |
| assertTrue( |
| 'The DeferredList errback should have canceled all pending inputs.', |
| res instanceof Deferred.CanceledError); |
| return null; |
| }); |
| addCatchAll(c); |
| |
| // Shouldn't cause any more callbacks on the DeferredList. |
| c.callback('C'); |
| } |
| |
| |
| function testNoConsumeErrors() { |
| var count = 0; |
| |
| var a = new Deferred(); |
| var dl = new DeferredList([a]); |
| |
| a.addErrback(function(res) { |
| count++; |
| return null; |
| }); |
| |
| addCatchAll(a, dl); |
| |
| a.errback('oh noes'); |
| assertEquals(1, count); |
| } |
| |
| |
| function testConsumeErrors() { |
| var count = 0; |
| |
| var a = new Deferred(); |
| var dl = new DeferredList([a], false, false, true); |
| |
| a.addErrback(neverHappen); |
| |
| addCatchAll(a, dl); |
| |
| a.errback('oh noes'); |
| assertEquals(0, count); |
| } |
| |
| |
| function testNesting() { |
| |
| function upperCase(res) { |
| return res.toUpperCase(); |
| } |
| |
| // Concatenates a list of callback or errback results into a single string. |
| function combine(res) { |
| return goog.array.map(res, function(result) { |
| return result[1]; |
| }).join(''); |
| } |
| |
| var a = new Deferred(); |
| var b = new Deferred(); |
| var c = new Deferred(); |
| var d = new Deferred(); |
| |
| a.addCallback(upperCase); |
| b.addCallback(upperCase); |
| c.addCallback(upperCase); |
| d.addCallback(upperCase); |
| |
| var dl1 = new DeferredList([a, b]); |
| var dl2 = new DeferredList([c, d]); |
| |
| dl1.addCallback(combine); |
| dl2.addCallback(combine); |
| |
| var dl3 = new DeferredList([dl1, dl2]); |
| dl3.addCallback(combine); |
| dl3.addCallback(function(res) { |
| assertEquals('AbCd', res); |
| }); |
| |
| addCatchAll(dl1, dl2, dl3); |
| |
| a.callback('a'); |
| c.callback('c'); |
| b.errback('b'); |
| d.errback('d'); |
| } |
| |
| |
| function testGatherResults() { |
| var a = new Deferred(); |
| var b = new Deferred(); |
| var c = new Deferred(); |
| |
| var dl = DeferredList.gatherResults([a, b, c]); |
| |
| dl.addCallback(function(res) { |
| assertArrayEquals(['A', 'B', 'C'], res); |
| }); |
| |
| addCatchAll(dl); |
| |
| b.callback('B'); |
| a.callback('A'); |
| c.callback('C'); |
| } |
| |
| |
| function testGatherResultsFailure() { |
| var a = new Deferred(); |
| var b = new Deferred(); |
| var c = new Deferred(); |
| |
| var dl = DeferredList.gatherResults([a, b, c]); |
| |
| var firedErrback = false; |
| var firedCallback = false; |
| dl.addCallback(function() { |
| firedCallback = true; |
| }); |
| dl.addErrback(function() { |
| firedErrback = true; |
| return null; |
| }); |
| |
| addCatchAll(dl); |
| |
| b.callback('B'); |
| a.callback('A'); |
| c.errback(); |
| |
| assertTrue('Errback should be called', firedErrback); |
| assertFalse('Callback should not be called', firedCallback); |
| } |
| |
| |
| function testGatherResults_cancelCancelsChildren() { |
| var canceled = []; |
| var a = new Deferred(function() { |
| canceled.push('a'); |
| }); |
| var b = new Deferred(function() { |
| canceled.push('b'); |
| }); |
| var c = new Deferred(function() { |
| canceled.push('c'); |
| }); |
| |
| var dl = new DeferredList([a, b, c]); |
| |
| var firedErrback = false; |
| var firedCallback = false; |
| dl.addCallback(function() { |
| firedCallback = true; |
| }); |
| dl.addErrback(function() { |
| firedErrback = true; |
| return null; |
| }); |
| |
| addCatchAll(dl); |
| |
| b.callback('b'); |
| dl.cancel(); |
| |
| assertTrue('Errback should be called', firedErrback); |
| assertFalse('Callback should not be called', firedCallback); |
| assertArrayEquals(['a', 'c'], canceled); |
| } |
| |
| |
| function testErrorCancelsPendingChildrenWhenFireOnFirstError() { |
| var canceled = []; |
| var a = new Deferred(function() { |
| canceled.push('a'); |
| }); |
| var b = new Deferred(function() { |
| canceled.push('b'); |
| }); |
| var c = new Deferred(function() { |
| canceled.push('c'); |
| }); |
| |
| var dl = new DeferredList([a, b, c], false, true); |
| |
| var firedErrback = false; |
| var firedCallback = false; |
| dl.addCallback(function() { |
| firedCallback = true; |
| }); |
| dl.addErrback(function() { |
| firedErrback = true; |
| return null; |
| }); |
| |
| addCatchAll(dl); |
| |
| a.callback('a') |
| b.errback(); |
| |
| assertTrue('Errback should be called', firedErrback); |
| assertFalse('Callback should not be called', firedCallback); |
| assertArrayEquals('Only C should be canceled since A and B fired.', |
| ['c'], canceled); |
| } |
| |
| |
| function testErrorDoesNotCancelPendingChildrenForVanillaLists() { |
| var canceled = []; |
| var a = new Deferred(function() { |
| canceled.push('a'); |
| }); |
| var b = new Deferred(function() { |
| canceled.push('b'); |
| }); |
| var c = new Deferred(function() { |
| canceled.push('c'); |
| }); |
| |
| var dl = new DeferredList([a, b, c]); |
| |
| var firedErrback = false; |
| var firedCallback = false; |
| dl.addCallback(function() { |
| firedCallback = true; |
| }); |
| dl.addErrback(function() { |
| firedErrback = true; |
| return null; |
| }); |
| |
| addCatchAll(dl); |
| |
| a.callback('a') |
| b.errback(); |
| c.callback('c') |
| |
| assertFalse('Errback should not be called', firedErrback); |
| assertTrue('Callback should be called', firedCallback); |
| assertArrayEquals('No cancellations', [], canceled); |
| } |
| |
| |
| function testInputDeferredsStillUsable() { |
| var increment = function(res) { |
| return res + 1; |
| }; |
| var incrementErrback = function(res) { |
| throw res + 1; |
| }; |
| |
| var aComplete = false; |
| var bComplete = false; |
| var hadListCallback = false; |
| |
| var a = new Deferred().addCallback(increment); |
| var b = new Deferred().addErrback(incrementErrback); |
| var c = new Deferred(); |
| |
| var dl = new DeferredList([a, b, c]); |
| |
| a.callback(0); |
| a.addCallback(increment); |
| a.addCallback(function(res) { |
| aComplete = true; |
| assertEquals( |
| 'The "a" Deferred should have had two increment callbacks.', |
| 2, res); |
| }); |
| assertTrue('The "a" deferred should complete before the list.', aComplete); |
| |
| b.errback(0); |
| b.addErrback(incrementErrback); |
| b.addErrback(function(res) { |
| bComplete = true; |
| assertEquals( |
| 'The "b" Deferred should have had two increment errbacks.', |
| 2, res); |
| }); |
| assertTrue('The "b" deferred should complete before the list.', bComplete); |
| |
| assertFalse('The list should not fire until every input has.', dl.hasFired()); |
| c.callback(); |
| assertTrue(dl.hasFired()); |
| |
| assertFalse(hadListCallback); |
| dl.addCallback(function(results) { |
| hadListCallback = true; |
| |
| var aResult = results[0]; |
| var bResult = results[1]; |
| var cResult = results[2]; |
| |
| assertTrue(aResult[0]); |
| assertEquals( |
| 'Should see the result from before the second callback was added.', |
| 1, aResult[1]); |
| |
| assertFalse(bResult[0]); |
| assertEquals( |
| 'Should see the result from before the second errback was added.', |
| 1, aResult[1]); |
| |
| assertTrue(cResult[0]); |
| }); |
| assertTrue(hadListCallback); |
| |
| addCatchAll(dl); |
| } |
| |
| </script> |
| </body> |
| </html> |