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.
Related
I have a model hook on a route that ends by returning a Promise like:
return route.store.queryRecord(model, {username: params.username});
This works great except it doesn't trigger the "error" action that is anywhere in the chain. It is getting a 404 and logging the error in the console.
If I change the call to "find" with a id that doesn't exist it throws the same 404, but calls the transition to the error state. I cannot use the find for several reasons. Am I missing something simple?
Using ember 2.4.
Looks like queryRecord does not catch errors that are raised in the promise as we can see here: https://github.com/emberjs/data/blob/v2.5.3/addon/-private/system/store/finders.js#L194
What you can do in this case is to encapsulate the call to store.query in a Promise that you would return in your routes model hook, analyze the result of store.query and reject the encapsulating Promise if you get a 404.
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.
It looks like the latest versions of ember-data removed the rejectionHandler. Here is the old code https://github.com/emberjs/data/blob/4764b5d70c41c133edcbd1822bc587483c39e180/packages/ember-data/lib/adapters/rest_adapter.js#L11-L15 and example usage https://github.com/emberjs/data/blob/4764b5d70c41c133edcbd1822bc587483c39e180/packages/ember-data/lib/adapters/rest_adapter.js#L372.
I was using this to handle 401 unauthorized status codes from my server. Can I accomplish the same thing using the latest ember-data? I know I could pass a second function to all find and save calls to handle failure. But how to do that application wide?
To do that application-wide you should use the global error handling capabilities of the router.
App.ApplicationRoute = Ember.Route.extend({
actions: {
error: function(error, transition) {
//If error was a 401, do something...
}
}
});
See How to do cool stuff with the new Router API
I've recently been porting a number of Ember apps I maintain to RC 8 and ran into this.
Before the router facelift landed I would sometimes manage control flow via promises returned by Ember Data find calls.
For example:
SomeRoute = Ember.Route.extend({
model: function(params) {
var resolve = function(model) { return model; };
var route = this;
var reject = function() { this.transitionTo('someOtherRoute'); };
return SomeModel.find(params.some_model_id).then(resolve, reject);
}
...
});
With the recent changes, it is now possible to handle errors created in model callbacks via the error action:
SomeRoute = Ember.Route.extend({
// note: model callback no longer needed--default suffices
actions: {
error: function(reason, transition) {
// check the reason object to determine how/if to handle this error
this.transitionTo('someOtherRoute');
}
}
...
});
I much prefer the latter approach as it makes the code easier to read and better separates concerns.
This works well in most cases but I encountered an issue in an app that uses nested routes. I've included a simplified example followed by a jsbin that demonstrates the issue.
Lets say we want to show Articles that belong to Authors and the URLs look like: /authors/:author_slug/articles/:article_slug. We want to redirect to a Not Found page when someone tries to view an article that doesn't exist.
When managing control flow in the model callback as above, you can browse to /authors/some_author/articles/some_invalid_slug and be redirected to /authors/some_author/articles/not_found as expected.
However, if the redirect to the Not Found page is instead managed via the error action, the parent context is lost at some point and you end up at /authors/undefined/articles/not_found.
You can see this in the following jsbins:
http://jsbin.com/eJOXifo/1#/authors/schneier/articles/12345
(redirects to http://jsbin.com/eJOXifo/1#/authors/schneier/articles/not_found)
http://jsbin.com/oNaWelo/1#/authors/schneier/articles/12345
(redirects to http://jsbin.com/oNaWelo/1#/authors/undefined/articles/not_found)
Does anyone know why this happens or how to avoid it?
Notes:
I know this doesn't have anything to do with Ember Data. However, implementing something equivalent without Ember Data just makes the example more complicated without adding anything
There are a few small hacks to make Ember Data work as expected in jsbin:
I'm preloading the parent model to avoid having to load it from anywhere.
I'm not doing anything special to provide data for the child model. The app just makes a request to http://jsbin.com/articles/12345. This actually returns a 200 but bombs anyway because the response is html. An API that correctly returns a 404 response gives the same behvaiour.
I remember a while ago reading about some service that could be used to build fake API responses for use with services like jsfiddle or jsbin. If anyone knows what it is please comment.
Your right that the parent context is getting lost. The trick is to extract that context from the transition and pass it as an argument when calling transitionTo. So:
App.ArticlesArticleRoute = Em.Route.extend({
actions: {
error: function(reason, transition) {
console.log('in error handler');
this.transitionTo('articles.notFound', transition.resolvedModels.authors);
}
}
});
With this change, visiting the url:
http://jsbin.com/iVOYEvA/2#/authors/schneier/articles/my-fake-article
will redirect to:
http://jsbin.com/iVOYEvA/2#/authors/schneier/articles/not_found
In my route I have a method that tries to request a list of models from the server
model: ->
App.MyModel.find
projectId: (#modelFor "project").id
Now obviously sometimes this might return a 404.
At the moment when this happens, Ember just stops doing anything. No view is rendered, no controller is setup.
So how can I properly handle the 404 (ie show an error view)?
Bad news: right now, ember-data doesn't do anything when it gets a 404 on find(). At all. The model sits in the 'loading' state forever.
There are no non-completely-stupid options, here, in my opinion. What I would probably do as a last resort is add a notFound attribute on my DS.Model, and instead of returning 404, return JSON with notFound set to true. It's painful, I know...
--- I had originally offered a solution of overriding find in a subclass of RESTAdapter. Then I noticed that find DOES NOT get passed the record instance it is supposedly loading. So, no go on handling 404s by putting the record into an error state.
[NOTE: ember-data has changed dramatically since March 2013, the information in this answer may no longer be operative]
Incidentally, the "new" BasicAdapter was just released now. The question for me was, will this make things easier to handle 404 errors.
Approach #1
My first approach - similar to what Christopher was suggesting - was to add an additional field containing the HTTP status.
status: DS.attr("number");
And then I used this AJAX call:
$.getJSON(url, data).then(null, function(xhr) {
return {
id: id,
statusCode: xhr.status
};
}).always(function(data) {
return process(data).load();
});
What this does is to transform the error response (xhr) to a hash containing the requested id and the status code. Finally, the successful result or the failed hash are passed to the store.
This kind of works, but isn't very practical: When you show a list of all model instances those "mock" instances have to be filtered out manually.
Approach #2
Another idea was to create a special error model.
App.Error = App.Model.extend({
status: DS.attr("number")
});
And the according query:
$.getJSON(url, data).then(null, function(xhr) {
return App.store.load(App.Error, {}, {
id: 0,
status: xhr.status
});
}).done(function(data) {
return process(data).load();
});
This will load and create a new instance of the error model and put it into the store.
The problem with this is that Ember wasn't really "accepting" this. The application just stopped routing, doing nothing anymore. So this seems like a dead end as well :(
I hit this issue as well today.
However, after looking at the source, it appears the model is actually setup to utilize Ember.Evented, and we can add our own handlers for these cases.
The two events that caught my eye were becameError and didLoad.
In my case I was able to do something like the following:
// Grab a user by id.
var user_rec = App.User.find( user.id );
// Oh no! Error!
user_rec.on('becameError', function() {
alert('I could not find you!');
});
// We should be good! Proceed!
user_rec.on('didLoad', function() {
alert('Your email: '+this.get('email'));
});
Here's the source on github: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/model/model.js
Hopefully, if this is indeed the way we should be handling things, there will be some added documentation in the guides in the near future.