0

I have a long chain of promises that wind through a module of my code. I don't know beforehand how many promises I'll wind through, nor do I have scope from any one promise to any other other promise (meaning I can't do a Promise.join()).

My problem is that I have then() callbacks attached to multiple promises on this chain. How can I control which one is fired last?

UPDATE: Here's a simplified example:

var aFn = function () {
  return bFn().then(someFn);
};

var bFn = function () {
  return new Promise(function (resolve, reject) {
    if (a) return resolve();
    else return reject();
  });
};

aFn().then(anotherFn);

My problem is that the .then() in aFn().then(anotherFn) gets called before bFn().then(someFn).

Here are a few code snippets that helps illustrate my issue:

strategy.js

execute: function (model, options) {
  options.url = this.policy.getUrl(model, this.method, options);
  options.collection = this.policy.getCollection(model, options);
  options.model = model;
  // This is a Promise that eventually calls get()
  return this.sync(model, options);
},

get: function (key, options) {
  var updateCollection = this._getUpdateCollection(options);
  var getFromCache = _.bind(this.store.get, this.store, key, options);
  if (updateCollection) {
    // updateCollection received a promise from store-helpers.js:proxyGetItem()
    return updateCollection().then(
      function (collection) {
        var modelResponse = collection.policy.findSameModel(collection.raw, options.model);
        return modelResponse ? modelResponse : getFromCache();
      },
      getFromCache
    );
  } else {
    return getFromCache();
  }
},

_getUpdateCollection: function (options) {
  var collection = options && options.collection;
  var collectionControl = collection && collection.sync && collection.sync.hoardControl;
  if (collection && collectionControl) {
  var collectionKey = collectionControl.policy.getKey(collection, options);
  return _.bind(function () {
    // store.get() passes the returned promise of store-helpers.js:proxyGetItem()
    return this.store.get(collectionKey, options).then(function (rawCollection) {
      return {
        control: collectionControl,
        policy: collectionControl.policy,
        key: collectionKey,
        raw: rawCollection
      };
    });
  }, this);
}

},

store.js

// Just a proxy function that wraps a synchronous get get: function (key, options) { return this.getItem.apply(this, arguments); },

getItem: StoreHelpers.proxyGetItem

store-helpers.js

proxyGetItem: function (key, options) {
  return Hoard.Promise.resolve()
    .then(_.bind(function () {
      return this.backend.getItem(key);
    }, this))
    .then(function (raw) {
      var storedValue = JSON.parse(raw);
      if (storedValue !== null) {
        return storedValue;
      } else {
        return Hoard.Promise.reject();
      }
   });
},

In a very different part of the app I also have:

var originalExecute = Hoard.Strategy.prototype.execute;
Hoard.Strategy.prototype.execute = function (model, options) {
    options.originalOptions = _.clone(options);
    if (options.saveToCacheFirst) 
        return originalExecute.call(this, model, options)
            .then(_.result(options.originalOptions, 'success'), _.result(options.originalOptions, 'error'));

    return originalExecute.call(this, model, options);
}

I would like for the .then() above to fire last, however when .resolve in store-helpers.js is fired, this last .then() callback is invoked.

seebiscuit
  • 4,905
  • 5
  • 31
  • 47
  • Have you tried promise.differ and returning a promise.resolve in each of your promises and then do a bluebird.all followed by a .then? – joncodo Apr 15 '15 at 18:21
  • Did you mean `promise.defer`? Anyway, much of the code base above comes from a third party lib. I'd rather not mess with it... Btw, I'm using cujojs/when. I'll update the tags. – seebiscuit Apr 15 '15 at 18:25
  • 1
    Multiple `.then()` handlers that need to be executed in a defined order should be chained. That causes them to be sequenced into the order of your choice: `a().then(...).then(...)`. You can also return a promise from a `.then()` handler and the chaining will not continue until that promise is itself resolved. – jfriend00 Apr 15 '15 at 18:41
  • But that's the thing, @jfriend00, I can't chain them. Promises are nested throughout several objects. I seem to be at the mercy of some promise callback queue. – seebiscuit Apr 15 '15 at 18:46
  • If this is mostly stuff from external libraries, you can't control the inner workings of functions you call in these libraries. They do what they do unless you're willing to edit their code. You can control the sequencing of one call you make vs. another, particularly if they return promises. So, if you are making four library calls that all return promises, you can control the sequencing of those four operations, but not control the inner workings of any of those operations. I honestly can't tell from your question what more specifically you want help with. – jfriend00 Apr 15 '15 at 18:54
  • 1
    You should simplify your example code to the actual problem – Jeff Voss Apr 15 '15 at 18:57
  • If you want a given operation to execute last, then you do something like `when.join(arrayOfPromises).then(lastoperation)` on all the promises you want to finish before your last operation is executed. – jfriend00 Apr 15 '15 at 18:57
  • 2
    Your code is successfully chaining promises already, even across the nexted function calls. What exactly is the problem you have, that `_.result` is called too early? Please mark the expected execution order in your code with comments. – Bergi Apr 15 '15 at 18:57
  • Also, your code seems a bit unrelated. What is `originalExecute`? Does it have something to do with `strategy.js`? – Bergi Apr 15 '15 at 19:03
  • @Bergi `originalExecute` calls ***`strategy.js:execute()`***. I should've added it. – seebiscuit Apr 15 '15 at 20:58
  • Yes, thanks. What about that `sync` method? – Bergi Apr 15 '15 at 23:49
  • "How can I control which one is fired last?" Simple answer - last in chain is last to fire. Typically a last .then() will be in the same code block as the first async call. In between, any number of other async operations may occur. The master chain (or, by deduction, any subordinate chain) doesn't need to be aware of what goes on in other functions. Providing every async function returns a promise to its caller, everything will sequence properly. – Roamer-1888 Apr 16 '15 at 07:57

1 Answers1

0

My problem is that the .then() in aFn().then(anotherFn) gets called before bFn().then(someFn)

No it doesn't. As you've written the example, that expression is equivalent to

bFn().then(someFn).then(anotherFn)

- and while the .then() method does get called before someFn, the anotherFn callback does not.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375