bsr
bsr

Reputation: 58662

Queuing promises

I use mbostock/queue for queuing few async operation. It is more to rate limit (UI generate few events, where the backend can process it slowly), and also to make sure they are processed sequentially. I use it like

function request(d, cb) {
 //some async oper
 add.then(function(){
   cb(null, "finished ")
 })
}

var addQ = queue(1);
addQ.defer(request) //called by few req at higher rates generated by UI

I already uses angular.js $q for async operation. So, do I have to use mbostock/queue, or can I build a queue out of $q (which is in spirit https://github.com/kriskowal/q)

Thanks.

Upvotes: 8

Views: 23907

Answers (3)

f1lt3r
f1lt3r

Reputation: 2223

Basic $q Chain Example

Yes you can build a chained queue using Angular's $q! Here is an example that shows you how you could use recursion to create a queue of any length. Each post happens in succession (one after another). The second post will not start until the first post has finished.

This can be helpful when writing to databases. If the database does not have it's own queue on the backend, and you make multiple writes at the same time, you may find that not all of your data is saved!

I have added a Plunkr example to demonstrate this code in action.

$scope.setData = function (data) {

  // This array will hold the n-length queue
  var promiseStack = [];

  // Create a new promise (don't fire it yet)
  function newPromise (key, data) {
    return function () {
      var deferred = $q.defer();

      var postData = {};
      postData[key] = data;

      // Post the the data ($http returns a promise)
      $http.post($scope.postPath, postData)
      .then(function (response) {

        // When the $http promise resolves, we also
        // resolve the queued promise that contains it
        deferred.resolve(response);

      }, function (reason) {
        deferred.reject(reason);
      });

      return deferred.promise;
    };
  }

  // Loop through data creating our queue of promises
  for (var key in data) {
    promiseStack.push(newPromise(key, data[key]));
  }

  // Fire the first promise in the queue
  var fire = function () {

    // If the queue has remaining items...
    return promiseStack.length && 

    // Remove the first promise from the array
    // and execute it 
    promiseStack.shift()()

    // When that promise resolves, fire the next
    // promise in our queue 
    .then(function () {
      return fire();
    });
  };

  // Begin the queue
  return fire();
};

You can use a simple function to begin your queue. For the sake of this demonstration, I am passing an object full of keys to a function that will split these keys into individual posts, then POST them to Henry's HTTP Post Dumping Server. (Thanks Henry!)

$scope.beginQueue = function () {

  $scope.setData({
    a: 0,
    b: 1,
    /* ... all the other letters of the alphabet ... */
    y: 24,
    z: 25

  }).then(function () {

    console.log('Everything was saved!');

  }).catch(function (reason) {
    console.warn(reason);
  });
};

Here is a link to the Plunkr example if you would like to try out this code.

Upvotes: 3

Michael Cole
Michael Cole

Reputation: 16217

The short answer is no, you don't need an extra library. Promise.then() is sufficiently "atomic". The long answer is: it's worth making a queue() function to keep code DRY. Bluebird-promises seems pretty complete, but here's something based on AngularJS's $q.

If I was making .queue() I'd want it to handle errors as well.

Here's an angular service factory, and some use cases:

/**
 * Simple promise factory
 */

angular.module('app').factory('P', function($q) {
  var P = $q;

  // Make a promise
  P.then = function(obj) {
    return $q.when(obj);
  };

  // Take a promise.  Queue 'action'.  On 'action' faulure, run 'error' and continue.
  P.queue = function(promise, action, error) {
    return promise.then(action).catch(error);
  };

  // Oook!  Monkey patch .queue() onto a $q promise.
  P.startQueue = function(obj) {
    var promise = $q.when(obj);
    promise.queue = function(action, error) {
      return promise.then(action).catch(error);
    };
    return promise;
  };

  return P;
});

How to use it:

.run(function($state, YouReallyNeedJustQorP, $q, P) {

  // Use a $q promise.  Queue actions with P

  // Make a regular old promise
  var myPromise = $q.when('plain old promise');

  // use P to queue an action on myPromise
  P.queue(myPromise, function() { return console.log('myPromise: do something clever'); });

  // use P to queue an action
  P.queue(myPromise, function() {
    throw console.log('myPromise: do something dangerous');
  }, function() { 
    return console.log('myPromise: risks must be taken!');
  });
  // use P to queue an action
  P.queue(myPromise, function() { return console.log('myPromise: never quit'); });


  // Same thing, but make a special promise with P

  var myQueue = P.startQueue(myPromise);

  // use P to queue an action
  myQueue.queue(function() { return console.log('myQueue: do something clever'); });

  // use P to queue an action
  myQueue.queue(function() {
    throw console.log('myQueue: do something hard');
  }, function() { 
    return console.log('myQueue: hard is interesting!');
  });
  // use P to queue an action
  myQueue.queue(function() { return console.log('myQueue: no easy days'); });

Upvotes: 1

Brian Vanderbusch
Brian Vanderbusch

Reputation: 3339

Chained Promises

Angular's $q implementation allows you to chain promises, and then handle resolves of those promises according to your own logic. The methods are a bit different than mbostock/queue, but the intent is the same. Create a function that determines how your defered will be resolved (creating a promise), then make these available to a higher level controller/service for specific resolution handling.

Angular uses $q.defer() to return promise objects, which can then be called in the order you wish inside your application logic. (or even skipped, mutated, intercepted, etc...).

I'll throw down some code, but I found this 7 minute video at egghead.io to be the best short demo: https://egghead.io/lessons/angularjs-chained-promises, and it will do a FAR better job of explaining. Thomas (the presenter) builds a very small flight dashboard app that queues up weather and flight data, and processes that queue when a user queries their itenerary. ThomasBurleson/angularjs-FlightDashboard

I will be setting up a smaller demonstration on codepen, using the situation of 'eating at a restaurant' to demonstrate this concept: http://codepen.io/LongLiveCHIEF/pen/uLyHx

Code examples here:

https://gist.github.com/LongLiveCHIEF/4c5432d1c2fb2fdf937d

Upvotes: 0

Related Questions