Permalink
Browse files

Promise based polling utility

periodically polls until cancelled or for a condition to become true
  • Loading branch information...
scothis committed Mar 15, 2012
1 parent a2f4f11 commit 95cf3d64db07c68f09fd2590c911f422ffe0cdc0
Showing with 317 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +3 −0 .travis.yml
  3. +24 −0 LICENSE.txt
  4. +6 −0 README.md
  5. +1 −0 latr.js
  6. +38 −0 package.json
  7. +119 −0 poll.js
  8. +4 −0 test/buster.js
  9. +121 −0 test/poll.js
View
@@ -0,0 +1 @@
+/node_modules
View
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+ - 0.6
View
@@ -0,0 +1,24 @@
+Open Source Initiative OSI - The MIT License
+
+http://www.opensource.org/licenses/mit-license.php
+
+Copyright (c) 2012 the original authors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
@@ -0,0 +1,6 @@
+latr
+====
+
+[![Build Status](https://secure.travis-ci.org/cujojs/latr.png?branch=master)](http://travis-ci.org/scothis/latr)
+
+Higher order functions built on top of when.js and asynchronous Promises.
View
@@ -0,0 +1 @@
+define([], {});
View
@@ -0,0 +1,38 @@
+{
+ "name": "latr",
+ "version": "0.0.1",
+ "description": "Async goodies based on when() and lightweight Promises.",
+ "keywords": ["promises", "when", "async", "latr"],
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "http://www.opensource.org/licenses/mit-license.php"
+ }
+ ],
+ "repositories": [
+ {
+ "type": "git",
+ "url": "https://github.com/cujojs/latr"
+ }
+ ],
+ "bugs": "https://github.com/cujojs/latr/issues",
+ "maintainers": [
+ {
+ "name": "Scott Andrews",
+ "web": "http://twitter.com/scothis"
+ }
+ ],
+ "dependencies": {
+ "when": "https://github.com/cujojs/when/tarball/dev"
+ },
+ "devDependencies": {
+ "buster": "~0.4"
+ },
+ "main": "./latr",
+ "directories": {
+ "lib": "./"
+ },
+ "scripts": {
+ "test": "buster test -e node"
+ }
+}
View
119 poll.js
@@ -0,0 +1,119 @@
+/** @license MIT License (c) copyright Scott Andrews */
+
+/**
+ * poll.js
+ *
+ * Helper that polls until cancelled or for a condition to become true.
+ *
+ * @author scothis@gmail.com
+ */
+
+(function(define) { 'use strict';
+define(['when', 'when/cancelable', 'when/delay'], function(when, cancelable, delay) {
+
+ var undef;
+
+ function F() {}
+ function beget(p) {
+    F.prototype = p;
+    var newPromise = new F();
+    F.prototype = null;
+    return newPromise;
+ }
+
+ /**
+ * Periodically exexcute the work function on the msec delay. The result
+ * of the work may be verrified to watch for a condition to become true.
+ * The returned deferred is cancellable if the polling needs to be
+ * cancelled before reatching a resolved state.
+ *
+ * The next vote is scheduled after the results of the current vote are
+ * verified and rejected.
+ *
+ * Polling may be terminated by invoking cancel() on the returned promise,
+ * or the work function may return a rejected promise.
+ *
+ * Usage:
+ *
+ * var count = 0;
+ * function doSomething() { return count++ }
+ *
+ * // poll until cancelled
+ * var p = poll(doSomething, 1000);
+ * ...
+ * p.cancel();
+ *
+ * // poll until condition is met
+ * poll(doSomething, 1000, function(result) { return result > 10 })
+ * .then(function(result) { assert result == 10 });
+ *
+ * // delay first vote
+ * poll(doSomething, 1000, anyFunc, true);
+ *
+ * @param work {Function} - function that is executed after every timeout
+ * @param msec {Number} - timeout in milliseconds
+ * @param [verifier] {Function} - function to evaluate the result of the
+ * vote. May return a {Promise} or a {Boolean}. Rejecting the promise
+ * or a falsey value will schedule the next vote.
+ * @param [delayed] {Boolean} - when true, the first vote is scheduled
+ * instead of immediate
+ *
+ * @returns {Promise}
+ */
+ return function poll(work, msec, verifier, delayed) {
+ var deferred, canceled;
+
+ canceled = false;
+ deferred = cancelable(when.defer(), function() { canceled = true; });
+ verifier = verifier || function() { return false; };
+
+ function certify(result) {
+ deferred.resolve(result);
+ }
+
+ function schedule(result) {
+ delay(msec).then(vote);
+ if (result !== undef) {
+ deferred.progress(result);
+ }
+ }
+
+ function vote() {
+ if (canceled) { return; }
+ when(work(),
+ function(result) {
+ when(verifier(result),
+ function(verification) {
+ verification ? certify(result) : schedule(result);
+ },
+ function() { schedule(result); }
+ );
+ },
+ deferred.reject
+ );
+ }
+
+ if (delayed) {
+ schedule();
+ }
+ else {
+ // if work() is blocking, vote will also block
+ vote();
+ }
+
+ // make the promise cancelable
+ deferred.promise = beget(deferred.promise);
+ deferred.promise.cancel = deferred.cancel;
+
+ return deferred.promise;
+ };
+
+});
+})(typeof define == 'function'
+ ? define
+ : function (deps, factory) { typeof module != 'undefined'
+ ? (module.exports = factory.apply(this, deps.map(require)))
+ : (this.latr_poll = factory(this.when, this.when_cancelable, this.when_delay));
+ }
+ // Boilerplate for AMD, Node, and browser global
+);
View
@@ -0,0 +1,4 @@
+exports['latr:node'] = {
+ env: 'node',
+ tests: [ './*.js' ]
+};
View
@@ -0,0 +1,121 @@
+(function(buster, poll, when, delay) {
+
+var assert, refute, fail;
+
+assert = buster.assertions.assert;
+refute = buster.assertions.refute;
+fail = buster.assertions.fail;
+
+function rejected(value) {
+ var d = when.defer();
+ d.reject(value);
+ return d;
+}
+
+function resolved(value) {
+ var d = when.defer();
+ d.resolve(value);
+ return d;
+}
+
+function failIfCalled(done, message) {
+ return function() {
+ fail(message || "should never be called");
+ done();
+ }
+}
+
+buster.testCase('latr/poll', {
+
+ 'should poll until canceled': function(done) {
+ var i, p, progback;
+
+ i = 0;
+ p = poll(function() { i++; return i }, 10);
+ progback = this.spy(function(result) { assert.equals(i, result); });
+
+ p.then(
+ failIfCalled(done, "should never be resolved"),
+ function(result) {
+ assert(progback.called);
+ done();
+ },
+ progback
+ );
+ delay(100).then(p.cancel);
+ },
+
+ 'should be canceled by rejected work': function(done) {
+ var p = poll(rejected, 10);
+
+ p.then(
+ failIfCalled(done, "should never be resolved"),
+ function(result) {
+ assert(true);
+ done();
+ },
+ failIfCalled(done, "should never receive progress")
+ );
+ },
+
+ 'should poll with delayed start': function(done) {
+ var i, p, progback;
+
+ i = 0;
+ p = poll(function() { i++; return i }, 10, function(result) { return result == 2 }, true);
+ progback = this.spy(function(result) { assert.equals(result, 1); });
+
+ p.then(
+ function(result) {
+ assert.equals(result, 2);
+ assert(progback.called);
+ done();
+ },
+ failIfCalled(done),
+ progback
+ );
+ },
+
+ 'should keep polling from rejected verification, stop for resolved verification': function(done) {
+ var i, p, progback;
+
+ i = 0;
+ p = poll(function() { i++; return i }, 10, function(result) { return i < 3 ? rejected() : resolved(true) });
+ progback = this.spy(function(result) { assert.equals(result, 2); });
+
+ p.then(
+ function(result) {
+ assert.equals(result, 3);
+ assert(progback.called);
+ done();
+ },
+ failIfCalled(done, "should never be rejected"),
+ progback
+ );
+ },
+
+ 'should keep polling from falsey resolved verification, stop for truethy resolved verification': function(done) {
+ var i, p, progback;
+
+ i = 0;
+ p = poll(function() { i++; return i }, 10, function(result) { return result < 3 ? resolved(false) : resolved(true) });
+ progback = this.spy(function(result) { assert.equals(result, 2); });
+
+ p.then(
+ function(result) {
+ assert.equals(result, 3);
+ assert(progback.called);
+ done();
+ },
+ failIfCalled(done, "should never be rejected"),
+ progback
+ );
+ }
+
+});
+})(
+ this.buster || require('buster'),
+ this.latr_poll || require('../poll'),
+ this.when || require('when'),
+ this.when_delay || require('when/delay')
+);

0 comments on commit 95cf3d6

Please sign in to comment.