Returning several promises on Route's model hook


#1

Hello,

I have a route which returns 4 promises wrapped inside an JS object. One of them is just a query to the model, but the other 3 are ajax requests.

I know that I can use Ember.RSVP.hash, but I want to be able to show the route’s template, even if one of the promises hasn’t been resolved or has been rejected.

The problem is that Route’s substate events (loading/error) are not called when I return the promises wrapped inside the Javascript object as follows:

export default Ember.Route.extend({
  
  model: function(params) {
    return { 
      user: this.store.find('user', params.trader_id),
      promise2Data: service.getPromise2(params.trader_id),
      promise3Data: service.getPromise3(params.trader_id),
      promise4Data: service.getPromise4(params.trader_id)
    } ;
  },

  actions: {
    loading: function() {
         alert("I am loading");
         // do something
    }
  }  
}

Why is this happening? is there any alternative to Ember.RSVP.hash that shows the route’s template even if one of the promises fails?


#2

If you wan to return promises even if one fails you could manage it manually. You can wrap the thing in a promise and handle the success and failure and return what you want.


#3

@varblob Do you mean wrapping the model’s return in a promise? I think that would be the same than returning a Ember.RSVP.hash, right?


#4

something like this

new Ember.RSVP.Promise(function(resolve, reject){
 Ember.RSVP.hashSettled({
    user: this.store.find('user', params.trader_id),
    promise2Data: service.getPromise2(params.trader_id),
    promise3Data: service.getPromise3(params.trader_id),
    promise4Data: service.getPromise4(params.trader_id)
  }).then( decide what to pass into resolve)
})

if you don’t want to display info about the errors you could just use the hashSettled call directly.


#5

You could bypass the model hook and use setupController instead.
I use this when I don’t want to block the UI


#6

Thanks @varblob, I will try that. However, I think that even using hashSettled, I won’t be able to show any resolved promise until all promises are fulfilled or rejected. On my example, each promise’s data could be displayed as it’s resolved… but loading/error events are not called.

@amk do loading/error events get called when a promise is rejected (404, etc)?


#7

@jsanglive Perhaps something like this


#8

Yeah, then you’d probably need to work up something on your own. An option would be to build an “aggregate” promise that resolves when any model resolves or rejects when all models have rejected. This would let you still use loading/error states according to the behavior you’re looking for. That doesn’t sound very hard, but it isn’t something available off the shelf in RSVP.

The problem there is that you’d probably still have to work with promises at the controller/component/template level, but that seems pretty much inevitable for this design.


#9

I think I misunderstood your intentions. If you want to handle the promises loading state after the route has transitioned, ie don’t block the transition waiting for promises to resolve then you were on the correct path with a plain javascript object.

If you want to fire events based on how the promises are proceeding you will have to do so manually. Maybe something like this, you could even write a function that runs through a js object and promise watches each then you can do stuff like check if everything is done loading:

model: function(params) {
  return { 
    user: this.promiseWatch(this.store.find('user', params.trader_id)),
    promise2Data: this.promiseWatch(service.getPromise2(params.trader_id)),
    promise3Data: this.promiseWatch(service.getPromise3(params.trader_id)),
    promise4Data: this.promiseWatch(service.getPromise4(params.trader_id))
  } ;
},

promiseWatch: function(promise){
  var _this = this;
  _this.send('loading');
  return promise.then(function(){
    _this.send('doneLoading');
  }).catch(function(){
    _this.send('error');
  });
}

You’ll have to play with the watch function as I’m not sure if you need to wrap it in a promise to call .then and .catch on or if you can just return something in the then( function(){ _this.send('doneLoading')

If you need a global is anything loading at all sort of thing you could also do something like this https://github.com/varblob/ember-community-cookbook/blob/master/recipees/global-ajax-service.js where you listen on $.ajaxComplete and other such events.


#10

@amk @awj @varblob thanks for all your examples. They’ve been pretty helpful.

The reason why I’m trying to go with this design, it’s because my route is a dashboard with statistics and charts retrieved from different API’s endpoints. So, I wanted to start showing info to the user as soon as something was available. Error event handling is necessary whether you request for a nonexistent user (404) or any other reason causing the promises to reject.


#11

I would recommend using model hook only for loading model. Model hook won’t be called in some situations – http://emberjs.com/api/classes/Ember.Route.html#method_model

You can always use afterModel for that:

export default Ember.Route.extend({
  
  afterModel: function(model, transition) {
    Ember.RSVP.hashSettled({
      promise2Data: service.getPromise2(transition.queryParams.trader_id),
      promise3Data: service.getPromise3(transition.queryParams.trader_id),
      promise4Data: service.getPromise4(transition.queryParams.trader_id)
    }).then(function(results){
      model.set('related_stuff', hash);
    })
  }  
}

Note that we don’t return promise inside afterModel to prevent loading state


#12

After and before model hooks are going away. https://github.com/ef4/rfcs/blob/routeable-components/active/0000-routeable-components.md


#13

Yes, but for now you can use them. As soon as that RFC gets implemented (Ember 2.2 ?) it will require few minutes to refactor this, since new attributes API can simulate after/before hooks pretty easily


#14

@H1D @varblob yes, alsosetupController (suggested above) will be deprecated. Thanks for your comments, I didn’t know that Model hook won’t be called in some situations.


#15

We are currently working on a similar system for our application, and looking for a way to display loading states independent of the other promises resolving or rejecting. What we decided on is injecting the store into components, and then handling loading, error and loaded states by hand.

Each component has the same setup data that they all need before they can proceed, in our case the user, so that is loaded in the route, and then passed into each component. Then the components query the server independently.

This way we can filter data within components, or switch out components without having to re-enter the model hook and re-query everything.

While this very much seems like an anti pattern, it seems like the least bad way to do this.

Thoughts?