12

I am learning AngularJS after converting from jQuery for a few years. And some bits are much more intuitive. Some not so much :).

I am trying to get my head around the use of promises, particularly $q in use with $http and there does not seem to be too much information around these two combined that I can find.

Why would I use promises in place of the success/error callback? They both make uses of callbacks in reality, so why is a promise considered better? E.g. I could set up a get(...) function like follows:

function get(url, success, error) {
    success = success || function () {};
    error = error || function () {};

    $http.get(url)
        .success(function (data) {
            success(data);
        })
        .error(function (error) {
            error(error);
        });
}

get('http://myservice.com/JSON/',
    function () {
        // do something with data
    },
    function () {
        // display an error
    }
);

Which is good(?) because it gives me complete control over what is happening. If I call get(...) then I can control any success/errors wherever get is called.

If I convert this to use promises, then I get:

function get(url) {
    return $http.get(url)
        .then(function (data) {
            return data;
        },
        function (error) {
            return error;
        });
}

get('http://myservice.com/JSON/')
    .then(function (data) {
        // do something with data
    });
    // cannot handle my errors?

Which is condensed, I agree; we also do not have to explicitly worry about the success/error callback, but I seem to have lost control over my error callback for a start - because I cannot configure a second callback to handle an error.

Which means that if I use this function in a service which can be used by multiple controllers, then I cannot update the UI to alert the user to an error.

Am I missing something? Is there a reason why promises is preferred? I cannot find an example why.

keldar
  • 6,152
  • 10
  • 52
  • 82
  • 1
    First of all full notation for `then` is `then(successCallback, errorcallback)`. So you can still handle errors. Then, you also have separate methods like `.success` and `fail` you can use. – dfsq Dec 10 '14 at 12:48
  • 2
    you can chain promises, is the single biggest advantage for me – harishr Dec 10 '14 at 12:51
  • @dfsq Oh OK - I am my error callback was not firing when I used `then` when calling `http://myservice.com/JSON/`. I will have to try again. Does .then(successCallback, errorCallback) do the same as .success(successCallback).fail(errorCallback)? – keldar Dec 10 '14 at 12:52
  • @harish - can you provide an example where this could be useful? – keldar Dec 10 '14 at 12:53
  • 2
    You can return new promises from `then` callbacks - another super cool advantage. Then error handling, when exceptions bubble up - one more thing. Callbacks are much less powerful. – dfsq Dec 10 '14 at 12:55

2 Answers2

25

Usually you'll deal with asynchronous tasks in Javascript with callbacks;

$.get('path/to/data', function(data) {
  console.log(data);
});

It works fine, but start to complicate when you go into whats called the 'callback hell';

$.get('path/to/data', function(data) {
  $.get('path/to/data2' + data, function(data2) {
    $.get('path/to/data3' + data2, function(data3) {
      manipulate(data, data2, data3);
    }, errorCb);
  }, errorCb);
}, errorCb);

The alternative is working with promises and defered object;

Deferreds - representing units of work
Promises - representing data from those Deferreds

Sticking to this agenda can assist to you in every extreme asynctask case:

  1. You have a regular call that need to get data from the server, manipulate it, and return to the scope
  2. You have multiple calls that each is depending on the precious one (cahin strategy)
  3. You want to send multiple (parallel) calls and handle their success in 1 block
  4. You want your code to be orginized (prevent dealing with handling results on controllers)

Your task is the easiest one to handle with $q and $http

function get(url) {
    var deferred = $q.defer();
    $http.get(url)
        .success(function (data) {
            deferred.resolve(data);
        })
        .error(function (error) {
            deferred.reject(error);
        });

    return deferred.promise;
 }

And calling the service function is the same

get('http://myservice.com/JSON/')
.then(function (data) {
    // do something with data
});
// cannot handle my errors?
Ben Diamant
  • 6,186
  • 4
  • 35
  • 50
  • This is awesome - thank you! What is the difference between my `get(...)` statement and yours? I return the `$http.get()` result where as you explicitly use `$q.defer()`. Is mine wrong? – keldar Dec 10 '14 at 13:04
  • 1
    The deffer object is a wrapper of calls, so I'm not returning the promise created by the $http call I'm returning the wrapping promis(would be the same if you cahin multiple calls) and just when it all resolved the "then" function of that promise get launched. And no - there's no right and wrong, your would work as well but its not the best guide line @keldar – Ben Diamant Dec 10 '14 at 13:06
  • the only way I can get my _chained_ error callback to fire is if I 'throw' an error in my first one? – keldar Dec 10 '14 at 13:26
  • Not sure I understand your question? @keldar – Ben Diamant Dec 10 '14 at 13:27
  • Sorry - I should have been clearer. My current `get(...)` function is OK (I think) with the example I showed above, by returning the `$http.get(...)` result. But if the errorCallback in my `get(...)` call is fired, I return the error - but any subsequent `then(...)` statements fire their success callback. Never their errorCallbacks? Is that clearer? – keldar Dec 10 '14 at 13:31
  • you can have an error and success handler for each "then" then(function(data) {}, function(error) {}).then(...) – Ben Diamant Dec 10 '14 at 13:46
  • I will set up another question - I think it is a separate issue to this thread and I cannot fit enough information into the comments :) – keldar Dec 10 '14 at 13:50
1

You can handle the error like this:

get('http://myservice.com/JSON/')
    .then(function (data) {
        // do something with data
    },
    function (error) {
        //do something with error
    });

But unfortunately since you have already caught the error then the final error won't be triggered. You will also have the same problem with success.

To get that to work you ned to use $q.

function get(url) {
    var deferred = $q.defer();

    $http.get(url)
        .success(function (data) {
            deferred.resolve(data);
        })
        .error(function (error) {
            deferred.reject(error);
        });

    return deferred.promise;
}

Also there is no need to pass in success and error functions because you can use promises instead.

Wayne Ellery
  • 7,888
  • 1
  • 28
  • 45
  • 1
    You need also to `throw` an error on the `get` function for this to work, since `return error` will not do the job. – Evandro Silva Dec 10 '14 at 12:53
  • That could explain why it was not working for me - I presume you would throw different errors in the error callback and the chained then will receive the errors in their callbacks? – keldar Dec 10 '14 at 13:00