10

I am trying to catch 404 errors in my ember app, and redirect to /not-found.

I have an errors action on my ApplicationController, and I have an RSVP.on('error') function too but the 404's aren't getting caught. I just get a 404 error thrown to my console from jQuery, but the error is not getting passed to the error handler.

Console Error

Errors initializer:

import Ember from 'ember';

var initialize = function(container) {
  var errorReporting = container.lookup("service:errorReporting");

  Ember.RSVP.on('error', function(err) {
    Ember.warn("Ember.RSVP error..... Logging error:");
    console.log(err);
    if (err.name && err.name === 'TransitionAborted') {
      Ember.debug("TransitionAborted error. Doesn't look like we should be catching these.");
    } else {
      container.lookup('route:application').send('error', err);
    }
  });

  window.onerror = function(err) { // window general errors.
    Ember.warn("Uncaught error (tripped window.onerror)..... Logging error:");
    console.log(err);
    errorReporting.report(err);
  };

};

export default {
  name: 'errors',
  initialize: initialize
};

The error action on my applicationRoute is huge (and I can post it), but it doesn't even seem to be getting called.

EDIT 1: Route Code

import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';

export default Ember.Route.extend(AuthenticatedRouteMixin, {

  titleToken: function(model) {
    return model.get('name');
  },

  model: function(params) {
    return this.store.find('location', params.location_id);
  }

});

EDIT 2: ApplicationRoute / Error handler

    error: function(err, transition) {
      if (!Ember.isNone(transition)) {
        transition.abort();
      }

      let errorHolder  = this._getErrorDataFrom(err);
      let errorMessage = this._getErrorMessageFrom(errorHolder);
      let isFourOhFour = (typeof(err.status) !== 'undefined' && err.status === 404) || errorHolder.reason === 'not_found';

      if (isFourOhFour) {
        return this.transitionTo('not-found');
      }

      let requireAuthentication = (errorHolder.reason === 'not_authenticated');
      if (requireAuthentication) {
        window.localStorage.setItem('toast-on-reload', errorHolder.message);
        return this.session.invalidate();
      }

      let isValidationError = ( errorHolder.reason === "validation_error" ||
                                ( !Ember.isNone(errorHolder.errors) && !Ember.isNone(errorHolder.message) ) );
      if (isValidationError) {
        this.toast.error(errorMessage);
        return;
      }

      let verificationRequired = (errorHolder.reason === "verification");
      if (verificationRequired) {
        this.toast.error(errorMessage);
        return this.transitionTo('verification');
      }

      let invalidRequest = (errorHolder.reason === 'unprocessable_entity');
      if (invalidRequest) {
        this.toast.error(errorMessage);
        return;
      }

      this.errorReporting.report(errorHolder);
      this.toast.error(errorMessage);
      return this.transitionTo('error');
    }
  },

  _getErrorDataFrom: function(obj) {
    if (!Ember.isNone(obj.responseJSON)) {
      return obj.responseJSON;
    } else if ( !Ember.isNone(obj.success) || !Ember.isNone(obj.errors))  {
      return obj;
    } else if (!Ember.isNone(obj.jqXHR) && !Ember.isNone(obj.jqXHR.responseJSON)) {
      return obj.jqXHR.responseJSON;
    } else {
      Ember.warn("No error handler available, using default ( {} ). Error:");
      console.log(obj);
      return {};
    }
  },

  _getErrorMessageFrom: function(errorHolder) {
    if ( typeof(errorHolder.errors) === 'object' && !Ember.isNone(errorHolder.errors.message) ) {
      return errorHolder.errors.message;
    } else if (!Ember.isNone(errorHolder.errors)) {
      return errorHolder.errors;
    } else if (!Ember.isNone(errorHolder.message)) {
      return errorHolder.message;
    } else {
      return "Sorry, something went wrong.";
    }
  }
Brandon
  • 3,091
  • 2
  • 34
  • 64
  • How are you calling `operators/location/this-does-not-exist`? – Kingpin2k Jun 03 '15 at 02:26
  • @Kingpin2k In this particular case I'm just visiting a route `myapp.com/locations/this-does-not-exist`. `operators/` is the API namespace. – Brandon Jun 03 '15 at 03:48
  • Visiting as in typing in the url in the address bar? – Kingpin2k Jun 03 '15 at 04:00
  • @Kingpin2k Yeah. Visiting any random URL like that does the same thing. The model hook fires, and does an ID lookup based on the last part of the URL. – Brandon Jun 03 '15 at 12:45
  • What's the model hook look like – Kingpin2k Jun 03 '15 at 15:48
  • @Kingpin2k updated question with the route that contains the model hook – Brandon Jun 03 '15 at 21:16
  • Did you try specifying an error handler within the location route? What does the location controller look like? Are you by any chancing overriding the default ErrorRoute? – Karim Baaba Jun 07 '15 at 00:08
  • @KarimBaaba Yeah I am over-riding the application/error route . I fear it may be trying to do too much? I will post it in the question. – Brandon Jun 07 '15 at 05:58
  • The most reliable, simplest way is to `catch` the error from the `find`. –  Jun 07 '15 at 06:46

1 Answers1

11

If you want to use the error event, then place its handler inside an actions hash in the application route.

Alternatively, consider the use of an error route. You can define this in pods/application/error, with templates, routes, and controllers just like any other route. See http://guides.emberjs.com/v1.10.0/routing/loading-and-error-substates/#toc_code-error-code-substates. The error code will be passed to that error route as its model.

Finally, in many cases it's most simple and reliable to catch the error from the find.

model: function(params, transition) {
    return this.store.find('location', params.location_id) .
        catch(err => this.send('ajaxError', err));
}

Then define the ajaxError action on your application route which does the same kinds of things you are doing in your error hook now. However, this will catch only ajax errors, not other sorts of errors that might occur during transitions, and be swallowed (or in your case reported by Ember.RSVP.on('error').

  • Thanks @torazaburo . This is probably what I'm looking for. I was really just hoping to figure out a way I could return "humanized" error messages from the API on an `error` key in the JSON response and have it handled in all cases. – Brandon Jun 08 '15 at 00:39