Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolve promise with an object with a "then" function

This is a bit of an abstract question, as I don't have a particular use-case right now. I've noticed that if you resolve a promise with a promise

var deferredA = $q.defer();
var deferredB = $q.defer();
deferredA.promise.then(function(result) {
  // Will get here once promiseB has been resolved.
});
deferredA.resolve(deferredB.promise);

than promiseA isn't actually resolved until promiseB has been resolved (and then promiseA is resolved with the value of promiseB's resolution). However, what if I wanted to resolve with a value of an object with a "then" function, as:

var deferred = $q.defer();
deferred.promise.then(function(result) {
  // Aim is to get here with result = {then:function(){}},
  // as though I had resolved a promise with a non-promise value,
  // but this function is never called
});
deferred.resolve({
  then: function() {
  }
});

then promiseA never actually gets resolved, as it assumed that the value is a promise, even though in the above example it's not, as in, it wasn't created with $q.defer(). There is an example plunkr at

http://plnkr.co/edit/Z8XUKzxHtGBKBmgPed2q?p=preview

Is there a way around this? If so, how?

Edit: clarified deferred/promise & put in example "then" callbacks.

like image 678
Michal Charemza Avatar asked Dec 11 '25 09:12

Michal Charemza


2 Answers

Solution

The then property that you're passing in is overriding the promise's then property. You instead want to return your object from within the success callback of the angular promises then function like this:

$scope.resolvePromise2 = function() {
    deferred2.resolve({
      then: function(successCB) {
        successCB({myResult1:'result1',
                  myResult2:'result2',
                  'then':function() {console.log("got here")}});
      }
    });
  };

Using the above your message is now called and you can call the then function in your property:

promise2.then(function(result) {
   //Now we get here
   $scope.messages2.push('Promise 2 then callback. Result is:' + result);
   result.then();
});

Here's your updated working plunker.

Issue/Why this works

Let's look at the Angular resolve():
resolve: function(val) {
    if (pending) {
      var callbacks = pending;
      pending = undefined;
      value = ref(val);

      if (callbacks.length) {
        nextTick(function() {
          var callback;
          for (var i = 0, ii = callbacks.length; i < ii; i++) {
            callback = callbacks[i];
            value.then(callback[0], callback[1], callback[2]);
          }
        });
      }
    }
  },

Looking at value = ref(val); followed by value.then(callback[0], callback[1], callback[2]); we see that Angular attaches the then function to the promise as a property and that the object you passed in overrides that property. So, in your case, the passed in then function is called instead of deferred.promise.then(function(result)....

But Angular calls your then function with the three callbacks (success, error, notify): value.then(callback[0], callback[1], callback[2]); that were saved off in var callbacks = pending;

So the solution is to call the first "success" callback inside your then function and pass it your object including the then property you want returned. Now the promise's then is called and receives your object including your then property

then: function(successCB) {
   successCB({myResult1:'result1',
              myResult2:'result2',
              'then':function() {console.log("got here")}});
}
like image 89
KayakDave Avatar answered Dec 14 '25 06:12

KayakDave


You said that {then : function(){...}} isn't a promise. Well, accordingly to the promise A+ spec it does.

The spec states that promises should be interoperable, that said if a promise its resolved with a thenable object or if the onFulfill callback of a promise returns a thenable. The promise returned by the then method of the first promise will be resolved as soon as the promise returned by the onFulfill or passed as parameter to the resolve method gets resolved.

So your promise will never resolve because your thenable object never gets "resolved", and never calls its onFulfill callback.

That's a little confusing, but lets see on practice.

var deferred = $q.defer();
deferred.promise.then(function(result) {
     // A promise should not be resolved with another
});

deferred.resolve({
    then: function() {
    }
});

The deferred's resolve method will see the then method of your object and think: "uhh, a sister promise, I will get resolved as soon it gets first". So it will call your then function with a callback to actually resolve your promise.

this way:

yourThenable.then(function(spectedData){ 
     // Your promise will resolve as soon as this callback gets called
     // But your then function doesnt call the onFulfill callback
     // Thats why it never resolves
}, function(spectedData){ 
     // Also if this second parameter gets called your promise will be rejected
});

I don't know why you are doing this but you should'n resolve a promise with a thenable. You can think in another ways to accomplish what you want.

Here is the easiest I can think:

deferred.resolve([{
   then: function() {
   }
}]);

The resolve method will lookup for a then method, but now your object is not a thenable, the 0 index of that array is.

deferred.promise.then(function(result) {
    // Now you have a result that is a Array
    // And the index 0 is your thenable object
});

But again, you should really think twice before keep doing that.

like image 22
pedroassis Avatar answered Dec 14 '25 05:12

pedroassis