Choosing Error Events or Error Substates in Ember

At the time of writing this:
Ember Version: 2.10.1
Ember Data: 2.10.0

Designing fault tolerance for ember apps.

Some of the Questions I've Run Into

  • Error Substates versus Error Events in Ember. When to choose what, why, and the differences in the approach.
  • Can they both work together or do you have to make a choice?
  • What different cases do they cover?
  • And beyond actions or substates- are there other ways you can notify users of errors?
  • In which cases should I use error action events, and for which do substates make the most sense?
  • When do neither make sense, and if there are other options and how to use them?
  • At what level should we define error notifications? Should all my nested routes/children have it? Or is the application level sufficient?

    The Ember Error/JSON Error Objects

    All Ember errors format follow the JSON-API Spec Error. 'error' is an array with error-keys as objects, that's currently how the JSON API formats errors. The top-level errors key in JSON is part of the auto-magic in Ember in how we can access it.

    "If a response is considered a failure, the JSON payload is expected to include a top-level key errors, detailing any specific issues."
    http://jsonapi.org/format/#error-objects Explains the error object via the API. So you have all the optional values that an errors object may have. errors = [], the values inside it are error keys which can be: id, links, status, code, title, detail, source, etc...

    So the error object MAY have all the keys or it won't. Which means depending on your Adapter and type of error you can get different error keys with their corresponding values.

    I highly recommend reading the Ember source code and how they have defined different Ember Error types and how Ember deals with them: Adapter Errors, Invalid Errors, Timeout Errors, Abort Errors, Unauthorized Errors, Forbidden Errors, Not Found Errors, Conflict Errors, Server Errors, etc.

    The Ember error in Ember code- writes down the function so you can see the Ember object at play- what it consists of, what you can display.

    Additional Reading:
    http://thejsguy.com/2016/01/09/handling-errors-with-ember-data.html

    Why not Substates all the time?

    I wrote about "error substates" in an earlier blog post in more detail.

    Frankly these are the easiest. They look clean and make a good impression. If my low standards were ever adhered to at work, I'd only be using them. Alas.

    Substates typically render an entire full-screen page. In some respects that is a dead-end. You may not want your user redirected to a giant error page every time something goes wrong. A better approach may be to nudge them with a message, or even render a smaller page within a component. You get the idea- the purpose is to not break the user-flow of the application just because an error has surfaced.

    Instead of a wide-page error options like we have now: We want to show them the search page again (only with an error UI message that tells them what went wrong). This way they can still work.

    DDAA: Let's talk about bubbling

    Substates mostly work with model hooks: model, beforeModel, afterModel. Ember uses asynchronous routing. Different hooks to visualize the different moving parts. To set up properties in the route that can pass data down to the template- from route to template: then setupController let’s you modify the behavior of the controller.

    We can "use {{model.message}} since the error will become the model via application-error route's setupController hook."

    this.modelFor(NameOfParentRoute) can be used when you want to access the model of your parents. Nested children routes can access the model of their parents.

    While all the error actions bubble up, you can pass messages (data) down- by setting those properties in the controller. You will set the error messages and any associated properties in the controller and the data does get passed down.

    This is part of the data-down, actions up approach in Ember. Heard about it but when designing error cases for the application, but saw what it meant in designing fault tolerance. All the error actions were bubbling up to the Application route.

    You don't need to define error action in sub-routes, nor do you need to do the same for substates for nested routes. The Application route handles this all for you- that is the Ember way.

    In fact, as a way of circumventing this bubbling, was forcing it to transition to other pages, which was not very Emebery and inappropriate way to tackle error actions:

    let currentURL = this.get('router.url'); console.log(currentURL); if (currentURL.includes("freight-bills")) { console.log("We're in FREIGHT route"); this.transitionToRoute('freight-bills-error'); }

    Even though the error actions bubble up- and show me the application-error.js page, I hardcoded a transition to take us to a different error page.
    No… Back to the drawing board.

    Error events bubble upwards- returning true is actually a quicker and cleaner way of sending errors from nested routes into the application route.

    Gotcha: Top level routes. Poorly named, were rearranged midway. Set out index path to one thing, had the application route at another. Ember console is the quickest fix.

    TYPICAL ERROR ACTION EVENTS

    A simple example of an error action event.

    actions: {

    error(error, transition) {

    alert('Sorry this page is taking so long to load!');

    this.transitionTo('application');

    }

    These are quite straight-forward to compose, however, you must decide to use actions or substates. It is currently not possible to use both together.

    Potential Error Cases

    Here are some of the error conditions your application may want to cover:
    • Adapter Errors
    • Bad IDs
    • 404s/not found
    • Permissions/Authentication:
    • List comes back with empty results
    • Timeout errors
    • Logic that caters to specific 400s & 500s errors.

    The above list out pretty general use cases. Your app can have several others or certain unique cases for which you may have to compose logic for depending on the complexity.

    Adapter Errors

    Success with requests is any 2xx or 304 response. A failed response: 422 (“Unprocessable Entity”) will be an invalid response, errors value.

    From the Guides:

    A DS.InvalidError is used by an adapter to signal the external API was unable to process a request because the content was not semantically correct or meaningful per the API. Usually this means a record failed some form of server side validation. When a promise from an adapter is rejected with a DS.InvalidError the record will transition to the invalid state and the errors will be set to the errors property on the record.

    REST Adapter Errors. This sub-topic is something another future blog post can cover because it can get exhaustive. But essentially this is when your data is gone because of an Apocalypse or your microservices/API/backend is switched off.

    So no JSON, no data, your adapter is terminated. When so much is lost: do we really have to go out of our way to display an error to the user? One could argue if no data then no app and therefore no errors either. Well that depends on your application- and even if it isn't enterprise level- it's not a bad idea to have a fail-safe.

    Gotchas: With microservices was turned off to replicate the error and write the tolerance code, the whole screen turned blank. Nothing was rendering. The solution which gets more detailed below is where modelFor() had to be set, so we could at least get the template sans data.

    Fun fact: The nature of your error object and how to retrieve messages from it changes depending on your adapter. The REST adapter is different from the JSON Adapter or Active Adapter. I mentioned this as a fun fact. It is not. It is actually quite frustrating because the documentation/methodology for each varies.

    Page Not Found

    One of the most common error conditions and often the first thing looked at is the "not found page" or the classical 404 error. Many companies use this opportunity to express themselves creatively.

    Instead of doing a substate- created a custom 404 path. Had logic but even commenting out makes it work- so the *path in the router is all the code that's required. You can get a 404 error page without the whole substates thing.

    In app/router.js
    this.route('not-found', {path: '*path'});

    A not-found substate will suffice in Ember's auto-magical way. But added a route handler for the page, in excess, since there were several error modifications so needed some fallback code. Thanks to the router, it's redundant.

    import Ember from 'ember';

    export default Ember.Route.extend({

    redirect() {

    const url = this.router.location.formatURL('/not-found');

    if (window.location.pathname !== url) {

    this.transitionTo('/not-found');

    }

    }

    });

    Bad IDs

    This is close to the not-found logic, but not quite. ID matching- when you go to the show page, a nested route, you typically refer to it's ID in the URL. A common extension of that logic is that if it is an invalid id therefore incorrect url- so 404. If you try replicating the error in your browser, by changing the ID and loading the page you may be surprised to see no error object. Well it's not the Router's job. What's the correct ID and what's not- that is not related to the url but the place the data is loaded.

    Permissions & Authentication

    Regarding the error case for permissions, it could be as simple as writing logic for a 403 'forbidden' case. Or it could be entirely subjective based on user permissions to your app and its directories and have nothing to do with the HTTP status code 403. Computed error messages might be a better solution.

    Empty Results

    If your app has to load data, and the results are nada.

    Timeout Errors

    "When it takes too long" isn't just the lyrics to the Weepies. Check your AJAX and async code. Best place to write fault tolerance for timeout errors is within your functions, but Ember does cater to the timeout errors.

    Logic for Client (400s) & Server Errors (500s)

    https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
    Status Codes & What they mean

    if (code) {

    if (code === 404) {

    controller.set('message', 'Page Not Found');

    } else if (code === 403) {

    controller.set('message', 'Access Forbidden');

    } else if (code === 502 || code === 503) {

    controller.set('message', 'Bad Gateway');

    } else {

    controller.set('message', 'Looks like we have a problem');

    }
    }

    You get the idea. Depending on how specific or general you want the messages to be.

    OTHER OPTIONS

    Rendering Substates into Templates

    Loading happens in a small UI instead of a full page.
    Here are some articles of how to render loading substates into an application, whose logic can be easily modified to implement error substates within a page:

  • http://cball.me/understanding-loading-substates-in-ember/
  • https://balinterdi.com/2014/06/18/indicating-progress-loading-routes-in-ember-dot-js.html

    Methods: render(), renderTemplate()

    You can render a template into another template’s outlet. "Each resource route (and thus each route level) creates an outlet for the level below to render content in." Parent has an {{outlet}} where children crawl into.

    renderTemplate: function() {

    this.render({ outlet: 'activities' });

    }

    Decorate your Substates

    You can also flip that logic on its head- and as you render a fullpage substate, if your app is adequately modularized, you can add the vital components inside of the substate. This prevents the dead-end approach, and gives your users the flexibility to move on to other actions or flows.

    Computed Properties setting error messages

    Computed properties throwing out custom messages depending on what the error event/status is.
    Created a computed property called errorMessage to display a custom message on the screen if there was a lens error (Adapter Error).

    This was done at the freight-bill route- in our search app. So neither events nor substates were working nor were they the type of solution we wanted- ended up doing Ember.computed properties.

    The following computed message is for displaying custom messages in case of Adapter Errors:
    In Controllers- for each of your nested routes:

    errorMessage: Ember.computed('lenses', 'model', function() {

    const lenses = this.get('lenses');

    const model = this.get('model');

    if (lenses instanceof Error) { 
      return "We are experiencing network congestion." 
    } 
    

    Even more approaches:

    -> Alert messages. Because popups mean business.

    -> Sometimes you just have to throw errors the good old fashion JavaScript way right in the route files. MENTION: try-catch

    -> An interesting undertaking would be to include it in an Ember Mixin. Would be useful for teams managing a lot of small products, based on what type of error they want to include in their project- they can choose the properties.

    Leveraging Ember Console

    Ember Console- if you view tree- you can see the route hierarchy. The Ember Inspector dev tool will show you a new route for application-error just by having either the route or template file in existence. If you wanted to write custom error events, you'd have to get rid of those files.
    (Picture of the tree)

    Tip: Add some breakpoints when debugging code, best way to visualize where the breakage occur.