In an ember.js application, how can we throw application errors (and have them bubble through the controller-route-application hierarchy?)
My use case is related to catching non ember-data ajax errors and handling them through the same path as ember data errors.
(i.e. when experiencing an error from a non-restful endpoint, allow that error to bubble through the application similar to ember-data errors)
If you want to throw errors, use throw new Error("Message");.
The user gets redirected to error route.
With Promises you can react on Exceptions, and handle them.
See: http://emberjs.com/api/classes/RSVP.Promise.html
Ember has its own EmberError class which sub classes the Javascript error class.
import EmberError from '#ember/error';
export default Route.extend({
/* Pseudo code... */
model() {
if(/* something bad happens */) {
throw new EmberError('Oh, no! Something bad happened!');
}
},
});
You can do something with it in an error action in your route. If you want it to bubble up, remember to return true.
actions: {
error(error) {
// do something
return true;
},
},
Ember automagically creates an error substate and a error template where you can display info about the error (without redirecting). You can show error info in application-error.hbs or error.hbs like so:
<h1>{{model.name}} {{model.status}}</h1>
{{model.stack}}
See the Ember substate guide for more options.
Related
I've set up an error substate route/controller/template according to http://guides.emberjs.com/v2.2.0/routing/loading-and-error-substates/. Manually browsing my app, I can trigger error conditions and get directed to the substate. Confirmed with Ember Inspector.
I'd like to automatically test the substate. However, Ember CLI's test runner fails any test when a route's model hook rejects. In other words, the test fails before I can navigate to the error substate.
How can I automatically test my error substate?
Ember: 2.2.0
Ember CLI: 1.13.13
Unfortunately it doesn't seem to be easy to do this in a clean manner.
In its internal tests, Ember uses bootApplication to the route which errors (see github) and is able to directly catch the error. Unfortunately if you try and do any form of try/catch or then/catch around a call to visit in your tests you will find it fails.
When you visit a link which results in an error substate from your acceptance test then Ember's defaultActionHandlers.error gets fired. By design it is not meant to be overridable. It calls logError which calls Ember.default.Logger.error.
So to test this substate we need to temporarily overwrite that method. We can also peek inside the ember container to access the currentRouteName like so (using sinon for the spying):
test('when there is an API error an error message is shown', function(assert) {
const emberLoggerError = Ember.Logger.error;
Ember.Logger.error = sinon.spy();
visit('/users/');
andThen(() => {
// This could be nicer and less private with `getOwner`
let { currentRouteName } = this.application.__container__.lookup('router:main');
assert.equal(currentRouteName, 'users.index_error', 'The current route name is correct');
assert.equal(Ember.Logger.error.callCount, 1, 'The error logger was called');
// Restore the Ember.Logger
Ember.Logger.error = emberLoggerError;
});
});
Things can get even more complicated though. If your visit happens inside a Promise (it did in our case because we were using ember-page-object for our tests) then you have more to deal with...
In a separate loop onerrorDefault of RSVP is triggered which calls Test.adapter.exception AND Ember.default.Logger.error (again!) - passing the stack. So in this case you need to stub and spy on Test.adapter.exception and expect Ember.default.Logger.error to have been called twice!
I have this route that currently, when I transition back to it, gives me this great big error with a stack trace that doesn't help me figure out what is going wrong.
Error while processing route: project.details Assertion Failed: calling set on destroyed object Error: Assertion Failed: calling set
on destroyed object
at new Error (native)
at Error.EmberError (http://starqa.fmr.com/assets/vendor.js:22615:21)
at Object.Ember.default.assert (http://starqa.fmr.com/assets/vendor.js:15716:13)
at Object.set (http://starqa.fmr.com/assets/vendor.js:26367:22)
at exports.default.mixin.Mixin.create.set (http://starqa.fmr.com/assets/vendor.js:41034:20)
at Ember.Object.extend.flushCanonical (http://starqa.fmr.com/assets/vendor.js:69769:14)
at ember$data$lib$system$relationships$state$has_many$$ManyRelationship.flushCanonical
(http://starqa.fmr.com/assets/vendor.js:71525:22)
at Queue.invoke (http://starqa.fmr.com/assets/vendor.js:11425:18)
at Object.Queue.flush (http://starqa.fmr.com/assets/vendor.js:11490:13)
at Object.DeferredActionQue
Through just going through my routes and commenting out stuff, I found this in my projects route:
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model: function(params) {
if (params.q) {
return this.store.find('project', params);
} else {
var _this = this;
Ember.run(function() {
_this.store.unloadAll('project');
});
return this.store.findAll('project', {reload: true});
}
}
And if I comment out lines 7-9:
/*
Ember.run(function() {
_this.store.unloadAll('project');
});
*/
then the error goes away and the transition worked. This bit of code was written by somebody else, and I think it has to do with refreshing the model from the store, but I can't figure out why it would cause this "calling set on a destroyed object" error.
Any help would be greatly appreciated.
Offhand it looks like this route serves both single project param (q is specified) and all projects, which offhand sounds like far from perfect design for ember. Ideally you should have two routes - the projects and project route. That's on the Ember architecture side.
On the functional side, this happens when you object was released by ember yet you try and access it a second time. I suspect this is happening as the Ember.run, runs the unload all in another loop, whereas I don't think that's what's needed. Try to remove the unload all from the ember loop, or most chances you can just run without it at all (as why do you want to remove all the object in the current cache each time you call the route?
this.store.unloadAll('project');
return this.store.findAll('project', {reload: true});
Or just leave it commented out (I think leaving it commented should work for you).
I am using latest ember-data library for persistence in my Ember application.
Following is my route definition:
export default Ember.Route.extend({
model: function() {
return this.store.find('language');
}
});
There are two possible response for this:
Normal scenario: {"language":{"id":123,"name": "English"}}
Error scenario: {"error-response":{"status":"398648","message": "internal error."}} This is a standard error message for all errors.
In both the case, the response is returned with the "http 200 success" status code. So this means that the ember tries to call the resolve callback and generates error for error scenario. I need to handle this for our model definition. Is there any way I can intercept this response and take alternate action?
You could do this in a serializer, check it in there (http://emberjs.com/api/data/classes/DS.RESTSerializer.html#method_normalize) or something along the likes of:
export default Ember.Route.extend({
model: function() {
return this.store.find('language').then(function(language){
//perform your check on the response here
//if language contains error-response this.transitionTo('errorpage')
//else return language -- that kinda thing.
});
}
});
First of all, returning HTTP code 200 while getting an error is more than strange.
Ember expects reasonable response in error situation. It means, if you getting 4xx error, ember will activate Ember.Route.error hook (http://emberjs.com/guides/routing/loading-and-error-substates/#toc_code-error-code-substates); if server responded validation error (default code in Ember - 422), than Ember.Adapter will call extractValidationErrors and so on. In short, if you get an error, treat it as an error, and not as a successful response.
I wrote some code using find method like below.
The adapter is RESTAdapter.
App.SessionManager = Em.Object.extend({
userIdChanged: function() {
App.User.find(this.get('userid')).then(
function(user) {
/* process something */
},
function(error) {
/* rejection handler */
/* I want to know what error returned by the server here.*/
/* but how? */
}
);
}.observes('userid'),
});
If my server returned some error(e.g. 401) the rejection handler called.
But it seems that the argument of handler doesn't have error information.
How can I know the error reason in rejection handler?
Handling of errors returned by the server is not yet fully implemented in ember data.
For reference, as stated in this blog post about ember-data:
We want to make error handling and dealing with client and server conflicts rock solid. A pull request from Paul Chavard is currently open and looks like a solid starting point for error handling. You should see much more development on this in the near future.
But there are some workaround you can do to get to that information in the meanwhile.
For example:
Ember.RSVP.configure('onerror', function(error) {
console.log(error.message);
console.log(error.stack);
});
Or use a dirty hack to get to that information. Since ember-data uses jQuery under the hood you can subscribe to ajaxError:
$(document).ajaxError(function(event, jqXHR, ajaxSettings, thrownError) {
// this will trigger anytime there is an error
});
Hope it helps.
you can also override this:
becameInvalid: function(record) {
// you can get errors just by doing record.errors, error keys must match
// the name of a field that you have defined in your model
}
and:
becameError: function(record) {
}
that's in case something happened in the server that caused an error. I just saw that you want to handle errors on promises, well, that's something different that I haven't dealt with. becameError might be fired.
UPDATE:
I just found something interesting that might help you: Ember-Data handling 401’s thanks to the new router, I haven't had the chance to use all those new features, but they look pretty cool.
I have a property that depends on another property. There is an error in my code that happens when the other property is present. The error is being swallowed by something, probably Ember. This makes debugging the error very hard. I have tried setting Ember.onerror to a function that just logs the error, it makes no difference.
Why is Ember swallowing this error, how can I make it not?
Code:
App.DashboardController = Ember.Controller.extend({
leaderboard: function() {
console.log("calling leaderboard");
var ces = this.get("engagements");
if (ces) {
console.log("before");
throw new Error("bad thing");
console.log("after");
}
console.log("done")
}.property("engagements")
})
Console log:
calling leaderboard
done
(setting engagements)
calling leaderboard
before
Version: Ember.js RC6, development
Turns out the property "engagements" was being set inside a promise fulfilment handler. This article explains how to catch errors that occur in those: http://blog.sensible.io/2013/06/10/promise-ate-my-homework-exception.html