Last Updated: February 25, 2016
·
4.599K
· longlivechief

Resolve Angular Promises from "Normal" Functions

What most Angular Promise Tutorials Fail to teach You: Creating the promise

So many times while learning how to use promises, or trying to use them in my Angular applications, I would discover pieces of functionality that needed to be a promise, but weren't a $resource (or one of it's derivates).

Most tutorials show a final result of something like this:

function(){
    var deferred = $q.defer(); // creates a deferred object
    var itFailed = deferred.reject();

   $http.get('http://some/api')
        .success() // a success function 
        .error(ifFailed('API failure. Try turning API server off then on.'))  // promise rejected, but considered resolved by using defer.reject()
        .then(function(data){
                 deferred.resolve(data);
        })
    return deferred.promise;
}

That's so helpful! But what if you need a promise for something other than an API call? Since the $http service is already a promise, all you really learned here was how to resolve a promise in .then() from a function that was already a promise. So what do you do when you need to resolve a promise from the result of a function call that isn't already a promise?

Promises are empty (english | javascript)

At first glance, this statement may seem pretty self-evident. Like everything in javascript, a promise is an object. But, a promise object is... a little odd. Promises are born when a deferred object is created, as a property on the deferred object. The promise is considered resolved when that property has a value, or the deferred function object (a method on $q) initiates another property we're all familiar with: a rejection.

Promises are created by deferring anything (english | javascript)

Another self-evident statement, but you see where this is going. Promises are the same thing in javascript as they are in real life, but in real life, we rarely consider what makes a promise a promise. I programming, where 0's don't just become 1's because we need them to, we have to be very clear about what makes a promise a promise.

A promise is formed when an action is delayed. In real life, that's a concept that we don't think through. If you tell your kids "we're going to Disneyland", you know you've just made a promise (even if you lied). But what you don't realize is you didn't just create a promise of the action of going to Disneyland, you also created the promise of having to make the decision to go Disneyland... up until the point when you actually walk through the gates.

In Angular development, we realize concept easily when we need to react to a promise. We have some great tools to do this: .then(), .success(), error(), and `promise['finally']. But to know when to use those, we need to connect the gap between javascript and reality.

So .when() can I use .then() (english | javascript)

The mistake most tutorials leave you with is the confusion regarding a promise object, and a deferred object. Neither can exist without the other, but a promise object is not a deferred object, It's a child object. To often after going through an Angular promise tutorial, I so developers do something like:


var goToDisneyland = $q.deferred();

deferred.then(buyMickeyMouseEars);

But if you've followed me to this point, then the piece that's missing has become apparent. The then in real life happens when an event occurs after the promise is fulfilled, but after the promise is made. The then you usually want in javascript only happens after the promise is resolved. (these consequences aren't the droids you're looking for).

This is critical. Since javascript methods prototypically inherit from their parents. Calling .then() on a deferred object won't throw any errors, because it's a completely valid thing to do. But it does mean that your then function will become a race condition, with a little bit of a jumpstart.

So unless you want to buy your Mickey Mouse ears after you get to Disneyland, and not from your neighbors garage sale, you'd better attach that then to the promise object, and not the deferred object.

So here's where most tutorials go wrong. They show you this:

var deferred = $q.defer();

But not this: <a id="missingLine"></a>

var promise = deferred.promise;

This is usually because most tutorials focus on the usage of resolving deferred values returned from an $resource, and thus your tutorial usually hands of the data to another component of the system to handle the then's of the world. So they'd look like this (using defer().promise in the return expression):

Your Goal: Buy Mickey Mouse Ears at Disneyland

Tutorial teaches you: How to arrive at Disneyland
What's Missing: teaching you how to wait until you get to Disneyland to buy your Mickey Mouse Ears
End Result: You bought Mickey Mouse ears from neighbors garage sale 3 months before trip
Why it matters: Dad got confused and really meant coney island, and they hate mice

.factory('myService', ['$http',function($http){
    var goToDisneyland = $q.defer();  

    // api call on directions to disneyland
    $http('http://directions to disneyland')   
        .then(function(data){                              //   <----- no promises here, just getting directions so I can fulfill that promise!
             goToDisneyland.resolve(directions);  // <---- assumes I'm going to buy my Mickey Mouse ears in another component
        }

    return goToDisneyland.promise  // <--- where the heck did this come from!?

}]);

If you, like many others, want to define your .then stuff inside your service (say you're being reasonable and your service is a model, and you want to send an email only after a successful customer purchase), you might wind up needing a .then expression in your model, just in case Dad's theme park knowledge is a little rusty.

Buying Mickey Mouse ears isn't a promise of an action, it's an action

.factory('myService', ['$http',function($http){
    var goToDisneyland = $q.defer();  // <---- implies I guess I think I just maybe made a promise?

    // ok, let's get to buying those ears, because in my other component won't let me on any rides unless I have authentic ones
     goToDisneyland().then(buyMickeyMouseEars);   // <--- seems legit, and doesn't throw errors. Let's go on some rides!
    promise.resolve();

    return goToDisneyland.promise  

}]);

Whoa there sonny, those are fakes

I've gone through the scenario, returned a promise, and defined a way to resolve the promise. but it winds up your promise executed before the right condition was fulfilled, and you likely came up with an undefined error, or returned a dataset that is in the wrong state. One line of code should clear all this up. Hint: I already showed you that line here

Next Time, we'll know better

Here's your service, now built on shame:

.factory('myService', ['$http',function($http){
    var makePromiseToGoToDisneyland = $.defer()  // or simply in most apps: var deferred = $q.defer() 
    var goToDisneyland = deferred.promise; // this was the promise that was inerently created

    // ok, let's get to buying those ears, because in my other component won't let me on any rides unless I have authentic ones
     goToDisneyland().then(buyMickeyMouseEars);   // <--- from the gift shop this time!! All day pass!!!
     promise.resolve();
    // hand master plan off to the wife for approval to make promise
    return makePromiseToGoToDisneyland.goToDisneyland  

}]);

Promises are empty (javascript | english)

// coming soon, less words more code alternative

Promises are created by deferring anything (english | javascript)

// coming soon, less words more code alternative

So .when() can I use .then() (english | javascript)

// coming soon, less words more code alternative

2 Responses
Add your response

To anyone who reads this, I missed a piece of punctuation or (9) that caused my code formatting to be off in the examples of the service. I have a gist that I've updated here: https://gist.github.com/LongLiveCHIEF/4c5432d1c2fb2fdf937d

over 1 year ago ·

Thanks for this, was a good read. I haven't created promises myself yet in AngularJS, at least not with Q.

over 1 year ago ·