What are recommended practices for preloading resources and handling errors in routes?


#1

Hi ember community,

I am working on an application that has a few set of resources that are used throughout various places in the application that I need to get from the server. Originally I was loading them with jQuery.when() to wait for all, then inside the app.js reopening the classes that needed these resources and assigned them to various controllers. This solution obviously isn’t ideal because these requests occur before ember has loaded and I would not have control over the application if anything went wrong.

I wanted to move to a solution where I preload the data and can navigate to an error route if any of these critical resources cannot be loaded.

My first solution was to use application initilalizers with dependency injections similar to the following:

Ember.Application.initializer({
           name: 'preloadAudiences',

           initialize: function (container, application) {
               application.deferReadiness();
               var audiencesRequest = $.getJSON("/api/audiences");
               audiencesRequest.then(function (audiencesResponse) {
                   return Data.Parse.audiences(audiencesResponse);
               }, function (jqXhr) {
                   Ember.Logger.warn("Request to load audiences failed: ", jqXhr.statusText);
               }).done(function (audiences) {
                   Ember.run.begin();
                   application.register('data:audiences', audiences, { instantiate: false, singleton: true } );
                   application.inject("controller:index", "audienceOptions", 'data:audiences');
                   application.advanceReadiness();
                   Ember.run.end();
               });
           }
       });

This solution was a little more semantically correct, because it was an actual application initializer and it was using ember to inject the data instead of reopenClass; however, functionally it did nothing different. The rejection handler for the promise just echoed the error that would have been printed based on an exception anyways and I still didn’t have the control to navigate somewhere else in case one of the requests failed because I would not have access to the router.

After learning about the new work on the router with asynchronous behavior my second solution was to use the beforeModel hook inside the applicationRoute. So far I have gotten the code to preload the resources for the happy path, but handling errors the way I want to has brought up many questions.

My current code looks something like this jsFiddle: http://emberjs.jsbin.com/UdagESo/11 Make sure you check out the javascript section, especially the ApplicationRoute to see what i’m referring to

In the real code, there are a few changes. Obviously, i have to get data from the server so instead of using Ember.RSVP.resolve with hardcoded data, i’m using Ember.$.getJSON. The second major difference is that unfortunately the format of the data returned from the server is not ideal for how I want to use it, so I have some parsing functions which restructure the data and return it so that it is suitable for binding to an Ember.Select view.

After going through this process here are the questions I have:

1. How to handle individual request failures when using Ember.RSVP.hash ?

I think ember RSVP.hash works really well for the happy path, but from what I understand, the entire hash is rejected at the first rejected promise. However, I would like to accept partial data. The only way I can see of doing this is supplying rejection handlers to all the individual promises in the hash and resolving them with the error message.


2. What is the best way to navigate to an error route with a message, assuming I could handle these errors properly?

The problem I have with my current solution is all of the generateController / controllerFor nonsense. Ideally I would like to just pass the error route a model and let the controller display properties of that model; however, ember only allows transitionTo(“error”, model) if the route is dynamic. Also, these dynamic routes must have some sort of unique identifier in the URL, which they use to look up in the model hook. Obviously, this is incorrect. Even if I did put the error string in the url, it cannot be retrieved by a string, and I don’t want people navigating to the error route with whatever message is in the url.


3. How can I resolve a rejected AJAX request?

When I use something like:

Ember.RSVP.reject("http://api/audiences").then(null, function (jqXhr) { Ember.Logger.log("Inside rejection handler"); return Ember.RSVP.resolve("some value"); }).then(function() { Ember.Logger.log("rejected promise was resolved anyways"); });

(*btw, it seems the block level code helper does not accept this string)

I can essentially override a rejected promise by returning a resolved promise and allow the execution to continue. However, in the real code which uses Ember.$.getJSON as mentioned earlier, the same trick does not work. Similar too this:

Ember.$.getJSON("http://api/audiences").then(null, function (jqXhr) { Ember.Logger.log("Inside rejection handler"); return Ember.RSVP.resolve("some value"); }).then(function() { Ember.Logger.log("rejected promise was resolved anyways"); });

I can get to the rejection handler, but the hash is rejected no matter what I return. I’ve tried returning Ember.RSVP.resolve(), basic scalar values, and even wrapping the original Ember.$.getJSON in an Ember.RSVP.promise(). but if an error occurs in one of those ajax request it seems to stay rejected no matter what. Which means my original solution to handle individual errors when using Ember.RSVP.hash will not work.


4. What is the recommended way to actually navigate to the error route / How to properly use route error action?

In the jsFiddle I’m obviously using the transitionTo directly so I can set the message etc which works, but in the event that an error occurs which I didn’t expect, it’s my understanding that this should hit the actions.error handler, but i don’t understand what parameter is passed into this function in order to know how to set a meaningful error message.

Congrats to those who suffered through my long post. I appreciate any suggestions.


#2

After reading the new guide on loading/error states for routes from Alex Matchneer, I think the answer to questions 2 and 4 are sufficiently answered. Basically Alex’s guide says that each route will implicitly have loading and error subroutes which can be automatically entered when waiting for a promise or when handling an error. (This concept is similar to how resources have implicit index subroutes) This works out much better than my global error route because it can preserve what portion of the application is still usable and can provide better context.

For more information see his posts: https://gist.github.com/machty/6944577

That leaves questions 1 and 3 which are more fitting for the title of this post. After re-reading my original post I realized it was easy to get distracted by the details of promises and error handling. The whole reason I ran into these problems was because I am trying to preload resources and I want to make sure I am headed in the right direction before I spend time solving the details.

That being said, I wanted to add a question that focuses back on the major topic.

What techniques are you using to preload remote data into your EmberJS web application? Are there better alternatives to the beforeModel hook in the ApplicationRoute, if so why?

Below is a bit more information about question 3 that might make things more clear. After reading more about promises, it seems jQuery promises are not a true implementation of the promise specification and that is why we cannot resolve after an errors occurs. One possible solution would be to not use jQuery promises at all but this isn’t ideal either. We could write a new getJson like the on seen on the RSVP homepage: https://github.com/tildeio/rsvp.js#basic-usage which might fix the problem with handling errors, but I imagine it would do more harm than good since the simple implementation on that page also misses many requirements of http requests. For instance, it resolves if the status is 200 regardless of the content size and doesn’t resolve for other status codes that should be considered successful like 201. In other words, it’s not worth it to rebuild $.ajax.