Permalink
Browse files

Adding cancelable, timeout, and delay helpers, and timed aggregator, …

…all with unit tests
  • Loading branch information...
1 parent e83c84b commit 0ff8c4545adf4c51f78d247f8db4d46b744d3dcc @briancavalier briancavalier committed Feb 2, 2012
Showing with 408 additions and 9 deletions.
  1. +2 −6 apply.js
  2. +65 −0 cancelable.js
  3. +59 −0 delay.js
  4. +5 −0 test/all.js
  5. +2 −2 test/apply.html
  6. +43 −0 test/cancelable.html
  7. +59 −0 test/delay.html
  8. +34 −1 test/test-config.js
  9. +43 −0 test/timeout.html
  10. +31 −0 timed.js
  11. +65 −0 timeout.js
View
@@ -1,11 +1,7 @@
-/**
- * @license Copyright (c) 2011 Brian Cavalier
- * LICENSE: see the LICENSE.txt file. If file is missing, this file is subject
- * to the MIT License at: http://www.opensource.org/licenses/mit-license.php.
- */
+/** @license MIT License (c) copyright B Cavalier & J Hann */
/**
- * apply.ja
+ * apply.js
* Helper for using arguments-based and variadic callbacks with any
* {@link Promise} that resolves to an array.
*
View
@@ -0,0 +1,65 @@
+/** @license MIT License (c) copyright B Cavalier & J Hann */
+
+/**
+ * cancelable.js
+ *
+ * Decorator that makes a deferred "cancelable". It adds a cancel() method that
+ * will call a special cancel handler function and then reject the deferred. The
+ * cancel handler can be used to do resource cleanup, or anything else that should
+ * be done before any other rejection handlers are executed.
+ *
+ * Usage:
+ *
+ * var cancelableDeferred = cancelable(when.defer(), myCancelHandler);
+ *
+ * @author brian@hovercraftstudios.com
+ */
+
+(function(define) {
+define(function() {
+
+ // private flag to indicate that a rejection is actually a cancel
+ var canceled = {};
+
+ /**
+ * Makes deferred cancelable, adding a cancel() method.
+ *
+ * @param deferred {Deferred} the {@link Deferred} to make cancelable
+ * @param canceler {Function} cancel handler function to execute when this deferred is canceled. This
+ * is guaranteed to run before all other rejection handlers. The canceler will NOT be executed if the
+ * deferred is rejected in the standard way, i.e. deferred.reject(). It ONLY executes if the deferred
+ * is canceled, i.e. deferred.cancel()
+ *
+ * @returns deferred, with an added cancel() method.
+ */
+ return function(deferred, canceler) {
+
+ // Add a cancel method to the deferred
+ deferred.cancel = function() {
+ deferred.reject(canceled);
+ };
+
+ // Replace deferred's promise with a promise that will always call canceler() first, *if*
+ // deferred is canceled. Can now safely give out deferred.promise
+ deferred.promise = deferred.then(null,
+ function cancelHandler(e) {
+ return e === canceled ? canceler(deferred) : e;
+ });
+
+ // Replace deferred.then to allow it to be called safely and observe the cancellation
+ deferred.then = deferred.promise.then;
+
+ return deferred;
+ };
+
+});
+})(typeof define == 'function'
+ ? define
+ : function (factory) { typeof module != 'undefined'
+ ? (module.exports = factory())
+ : (this.when_cancelable = factory());
+ }
+ // Boilerplate for AMD, Node, and browser global
+);
+
+
View
@@ -0,0 +1,59 @@
+/** @license MIT License (c) copyright B Cavalier & J Hann */
+
+/**
+ * delay.js
+ *
+ * Helper that returns a promise that resolves after a delay.
+ *
+ * @author brian@hovercraftstudios.com
+ */
+
+(function(define) {
+define(['./when'], function(when) {
+
+ var undef;
+
+ /**
+ * Creates a new promise that will resolve after a msec delay. If promise
+ * is supplied, the delay will start *after* the supplied promise is resolved.
+ *
+ * Usage:
+ * // Do something after 1 second, similar to using setTimeout
+ * delay(1000).then(doSomething);
+ * // or
+ * when(delay(1000), doSomething);
+ *
+ * // Do something 1 second after triggeringPromise resolves
+ * delay(triggeringPromise, 1000).then(doSomething, handleRejection);
+ * // or
+ * when(delay(triggeringPromise, 1000), doSomething, handleRejection);
+ *
+ * @param [promise] anything - any promise or value after which the delay will start
+ * @param msec {Number} delay in milliseconds
+ */
+ return function delay(promise, msec) {
+ if(arguments.length < 2) {
+ msec = promise >>> 0;
+ promise = undef;
+ }
+
+ return when(promise, function(val) {
+ var deferred = when.defer();
+ setTimeout(function() {
+ deferred.resolve(val);
+ }, msec);
+ return deferred.promise;
+ });
+ };
+
+});
+})(typeof define == 'function'
+ ? define
+ : function (deps, factory) { typeof module != 'undefined'
+ ? (module.exports = factory(require('./when')))
+ : (this.when_delay = factory(this.when));
+ }
+ // Boilerplate for AMD, Node, and browser global
+);
+
+
View
@@ -17,6 +17,11 @@ doh.registerUrl('when.reduce', '../../reduce.html');
doh.registerUrl('checkHandlers', '../../checkHandlers.html');
+// Helpers
+doh.registerUrl('when/cancelable', '../../cancelable.html');
+doh.registerUrl('when/timeout', '../../timeout.html');
+doh.registerUrl('when/delay', '../../delay.html');
+
doh.registerUrl('when/apply', '../../apply.html');
doh.run();
View
@@ -2,7 +2,7 @@
<html lang="en-US">
<head>
<meta charset="UTF-8">
- <title>check handler parameters Unit Tests</title>
+ <title>when/apply Unit Tests</title>
<script src="util/doh/runner.js"></script>
<script src="test-config.js"></script>
<script src="../apply.js"></script>
@@ -22,7 +22,7 @@
return sum;
}
- doh.register('checkHandlers', [
+ doh.register('when/apply', [
function testApplySpreadsArray() {
doh.assertEqual(15, when_apply(f)([1,2,3,4,5]));
},
View
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html lang="en-US">
+<head>
+ <meta charset="UTF-8">
+ <title>when/cancelable Unit Tests</title>
+ <script src="util/doh/runner.js"></script>
+ <script src="test-config.js"></script>
+ <script src="../when.js"></script>
+ <script src="../cancelable.js"></script>
+ <script>
+
+ (function(global, doh, async) {
+
+ var cancelable = when_cancelable;
+
+ doh.register('when/cancelable', [
+ function testHasCancel() {
+ var c = cancelable(when.defer(), function() {});
+ doh.assertTrue(typeof c.cancel == 'function');
+ },
+ function testCancelCausesRejection() {
+ var c = cancelable(when.defer(), function() { return 1; });
+ c.cancel();
+
+ return async.assertRejected(c, 1);
+ },
+ function testRejectDoesNotCallCanceler() {
+ var c = cancelable(when.defer(), function() { return 1; });
+ c.reject(2);
+
+ return async.assertRejected(c, 2);
+ }
+ ]);
+
+ doh.run();
+
+ })(window, doh, doh.asyncHelper);
+ </script>
+</head>
+<body>
+
+</body>
+</html>
View
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html lang="en-US">
+<head>
+ <meta charset="UTF-8">
+ <title>when/delay Unit Tests</title>
+ <script src="util/doh/runner.js"></script>
+ <script src="test-config.js"></script>
+ <script src="../when.js"></script>
+ <script src="../delay.js"></script>
+ <script>
+
+ (function(global, doh, async) {
+
+ var delay = when_delay;
+
+ function now() {
+ return (new Date()).getTime();
+ }
+
+ doh.register('when/delay', [
+ function testDelayResolves() {
+ return async.assertResolved(delay(0));
+ },
+ function testDelayResolvesWithValue() {
+ return async.assertResolved(delay(1, 0), 1);
+ },
+ function testDelayResolvesAfterDelay() {
+ var start, dohd;
+
+ dohd = new doh.Deferred();
+ start = now();
+
+ delay(50).then(
+ function() {
+ dohd.callback((now() - start) >= 50);
+ },
+ dohd.errback
+ );
+
+ return dohd;
+ },
+ function testDelayRejectsImmediately() {
+ var d, dohd;
+ d = when.defer();
+ d.reject(1);
+
+ return async.assertRejected(delay(d, 0), 1);
+ }
+ ]);
+
+ doh.run();
+
+ })(window, doh, doh.asyncHelper);
+ </script>
+</head>
+<body>
+
+</body>
+</html>
View
@@ -109,7 +109,40 @@
return dohd;
},
- assertSameContents: assertSameContents
+ assertSameContents: assertSameContents,
+ assertRejected: function(promise, value) {
+ var dohd, args;
+
+ dohd = new doh.Deferred();
+
+ args = arguments;
+
+ promise.then(
+ function(e) { dohd.errback(e); },
+ function(v) {
+ dohd.callback(args.length < 2 ? true : (value === v));
+ }
+ );
+
+ return dohd;
+ },
+ assertResolved: function(promise, value) {
+ var dohd, args;
+
+ dohd = new doh.Deferred();
+
+ args = arguments;
+
+ promise.then(
+ function(v) {
+ dohd.callback(args.length < 2 ? true : (value === v));
+ },
+ function(e) { dohd.errback(e); }
+ );
+
+ return dohd;
+ }
+
}
})(this, doh);
View
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html lang="en-US">
+<head>
+ <meta charset="UTF-8">
+ <title>when/timeout Unit Tests</title>
+ <script src="util/doh/runner.js"></script>
+ <script src="test-config.js"></script>
+ <script src="../when.js"></script>
+ <script src="../timeout.js"></script>
+ <script>
+
+ (function(global, doh, async) {
+
+ var timeout = when_timeout;
+
+ doh.register('when/timeout', [
+ function testTimeoutRejects() {
+ return async.assertRejected(timeout(when.defer(), 0));
+ },
+ function testTimeoutRejectsImmediately() {
+ var d = when.defer();
+ d.reject(1);
+
+ return async.assertRejected(timeout(d, 0), 1);
+ },
+ function testTimeoutResolvesImmediately() {
+ var d = when.defer();
+ d.resolve(1);
+
+ return async.assertResolved(timeout(d, 0), 1);
+ }
+
+ ]);
+
+ doh.run();
+
+ })(window, doh, doh.asyncHelper);
+ </script>
+</head>
+<body>
+
+</body>
+</html>
View
@@ -0,0 +1,31 @@
+/** @license MIT License (c) copyright B Cavalier & J Hann */
+
+/**
+ * timed.js
+ *
+ * Helper group that aggregates all time & delay related helpers. If you
+ * use several of these helpers it can be more convenient to use this module
+ * instead of the individual helpers
+ *
+ * @author brian@hovercraftstudios.com
+ */
+
+(function(define) {
+define(['./timeout', './delay'], function(timeout, delay) {
+
+ return {
+ timeout: timeout,
+ delay: delay
+ };
+
+});
+})(typeof define == 'function'
+ ? define
+ : function (deps, factory) { typeof module != 'undefined'
+ ? (module.exports = factory.apply(this, deps.map(require)))
+ : (this.when_timed = factory(this.when_timeout, this.when_delay));
+ }
+ // Boilerplate for AMD, Node, and browser global
+);
+
+
Oops, something went wrong.

0 comments on commit 0ff8c45

Please sign in to comment.