Rejecting promises with multiple arguments (like $http) in AngularJS

前端 未结 1 1922
说谎
说谎 2021-01-05 08:08

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

相关标签:
1条回答
  • 2021-01-05 08:52

    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:

    • Multiple promises in a service with multiple deferred promises
    • Try another method of deferring a result, like a long calculation

    And, as a bonus:

    • Try isolating the success/error callbacks into named methods of the service

    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.

    0 讨论(0)
提交回复
热议问题