Callbacks for $http
promises have multiple arguments: body, status, headers, config.
I'd like to create similar promise by hand but don't know how to do this. What I'd like to do is more or less:
myservice.action().then(function(status, message, config) {
// ...
});
I know I could pass object with keys to callback but would like to have similar convention as in $http
. I look at the angular sources, but either don't understand it fully or just can't do that right.
Do you know how to create promises that are able to pass multiple arguments to callback/errbacks?
As suggested in the comments, have a look at the $q
implementation of AngularJS. The docs are notorious for being... well hard to understand sometimes.
But anyway, let's try a short example. I do this in CoffeeScript, because I prefer it, but you should be able to compile the example at coffeescript.org if you want to.
Let's implement a service:
app = angular.module 'my.custom.services', ['your.other.modules']
app.factory 'Service', ['$http' , '$q', (http, q) ->
# this is your deferred result
deferred = q.defer()
get: ->
deferred.promise
]
This one is easy. It's just a service, which will make use of $q
and $http
, because a) we want some of this sweet promise-based stuff we're talking about and b) '$http' is a nice example in itself for something that can call asynchronous and which's result is not available right away.
The interesting part here is the get
part of the object that is returned here. Take note, that the service is implemented as a factory
, not as a service
. For the differences, see here. I usually think of it, as a "fancy" version for a service, where I just have some extra space for my own logic before exposing an API of the service (it really means something different, but thats for another story.
Anyway, get
will return the promise
of the deferred object when called. The promise
is an object, which exposes a then
method. When using this service, you'll probably inject it like:
app = angular.module 'my.custom.application', ['my.custom.services']
app.controller 'AppController', ['Service', (service)->
service.get() # then what?
]
As I mentioned, get
will just return a promise. Promises, as in real life, have to be resolved somewhere. So let's do that in the service - our promise will get resolved, whenever we're finished with the task we promised. This can be something like calling a URL via AJAX or a big calculation (anyone knows what the 117th Fibonacci number is?).
For our example, we use an http-call, as we do not now, whether or not and even when it will return to us:
app.factory 'Service', ['$http' , '$q', (http, q) ->
# this is your deferred result
deferred = q.defer()
# this is where http is used, this is started immediately, but takes a while
http.get('some-resource.json').then (response) ->
# now 'response' is the whole successful response, it has a data object with the payload
if !someCondition
deferred.resolve response.data #we have what we wanted
else
deferred.reject {error: "Failed", additional: "foo", number: 2} #we ran into some error
get: ->
deferred.promise
]
Based on someCondition
we can let the request fail on purpose if we want. If you want to try it your self, you can also use a timeout
like in the docs.
What happens now? Well, we still have that controller around:
app.controller 'AppController', ['Service', (service)->
service.get().then(successCallback, errCallback)
]
As I explained, the promise
exposes a then
method with the signature then(success, error)
, wherein success
and error
are functions that take whatever we resolved as arguments, e.g.:
app.controller 'AppController', ['Service', (service)->
successCallback = (data) ->
# we can work with the data from the earlier resolve here
scope.data = data
errCallback = (err) ->
# the error object, we got from the rejection
console.log err.error # => "Failed"
service.get().then(successCallback, errCallback)
]
if you want multiple values to be passed to the callbacks, I'd suggest you pass an object when resolving/rejecting the promise. You can also do named callbacks for the promise, like angular does in it's $http
implementation:
app.factory 'Service', ['$http' , '$q', (http, q) ->
# this is your deferred result
deferred = q.defer()
# [...]
get: ->
promise = deferred.promise
promise.success = (fn) ->
promise.then (data) ->
fn(data.payload, data.status, {additional: 42})
return promise
promise.error = (fn) ->
promise.then null, (err) ->
fn(err)
return promise
return promise
]
You essentially extend the promise given back by a method success
, which takes a single method as a callback, waits for the promise to be resolved and then uses the callback. You can do the same for an any other method if you want to (for hints, see the angular implementation)
In your controller, you can then use it like this:
service.get().success (arg1, arg2, arg3) ->
# => arg1 is data.payload, arg2 is data.status, arg3 is the additional object
service.get().error (err) ->
# => err
This is the basics, you can experiment if you want, I suggest you try the following:
And, as a bonus:
This example uses the $q
implementation in Angular, you can of course use another implementation for promises, like this one, which is the basis for $q
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With