crmUtil - Add crmQueue() helper for serializing crmApi() calls
authorTim Otten <totten@civicrm.org>
Thu, 16 Apr 2015 03:47:24 +0000 (20:47 -0700)
committerTim Otten <totten@civicrm.org>
Fri, 17 Apr 2015 00:35:57 +0000 (17:35 -0700)
ang/crmUtil.js
tests/karma/unit/crmUtilSpec.js

index f64292762bdb1f21d01a01e6aeacd5375c599224..b6a0d013104490866993547922f3a2a4368b7ea1 100644 (file)
     };
   }]);
 
+  // Wrap an async function in a queue, ensuring that independent async calls are issued in strict sequence.
+  // usage: qApi = crmQueue(crmApi); qApi(entity,action,...).then(...); qApi(entity2,action2,...).then(...);
+  // This is similar to promise-chaining, but allows chaining independent procs (without explicitly sharing promises).
+  angular.module('crmUtil').factory('crmQueue', function($q) {
+    // @param worker A function which generates promises
+    return function crmQueue(worker) {
+      var queue = [];
+      function next() {
+        var task = queue[0];
+        worker.apply(null, task.a).then(
+          function onOk(data) {
+            queue.shift();
+            task.dfr.resolve(data);
+            if (queue.length > 0) next();
+          },
+          function onErr(err) {
+            queue.shift();
+            task.dfr.reject(err);
+            if (queue.length > 0) next();
+          }
+        );
+      }
+      function enqueue() {
+        var dfr = $q.defer();
+        queue.push({a: arguments, dfr: dfr});
+        if (queue.length === 1) {
+          next();
+        }
+        return dfr.promise;
+      }
+      return enqueue;
+    };
+  });
+
   // Adapter for CRM.status which supports Angular promises (instead of jQuery promises)
   // example: crmStatus('Saving', crmApi(...)).then(function(result){...})
   angular.module('crmUtil').factory('crmStatus', function($q){
index 8c0daedb4fdd7830ab9cbc676a3dfd606fdc4bd1..3e32dbf06dae0d72e47f915c65c5870d6e4d119f 100644 (file)
@@ -103,4 +103,46 @@ describe('crmUtil', function() {
     });
 
   });
+
+  describe('crmQueue', function() {
+    var crmQueue, $q, $rootScope, $timeout;
+
+    beforeEach(inject(function(_crmQueue_, _$rootScope_, _$q_, _$timeout_) {
+      crmQueue = _crmQueue_;
+      $rootScope = _$rootScope_;
+      $q = _$q_;
+      $timeout = _$timeout_;
+    }));
+
+    function addAfterTimeout(a, b, ms) {
+      var dfr = $q.defer();
+      $timeout(function(){
+        dfr.resolve(a+b);
+      }, ms);
+      return dfr.promise;
+    }
+
+    it('returns in order', function(done) {
+      var last = null;
+      var queuedFunc = crmQueue(addAfterTimeout);
+      // note: the queueing order is more important the timeout-durations (15ms vs 5ms)
+      queuedFunc(1, 2, 25).then(function(sum) {
+        expect(last).toBe(null);
+        expect(sum).toBe(3);
+        last = sum;
+      });
+      queuedFunc(3, 4, 5).then(function(sum){
+        expect(last).toBe(3);
+        expect(sum).toBe(7);
+        last = sum;
+        done();
+      });
+
+      for (var i = 0; i < 5; i++) {
+        $rootScope.$apply();
+        $timeout.flush(20);
+      }
+    });
+  });
+
 });