A more advanced feature of the router is dealing with asynchronous logic. The following recipes explain this concept using promises.
In the route, Ember makes heavy use of promises. Promises are objects that represent an eventual value. We can use promises in our model.
$ ember g route application
If prompted to overwrite the template, type Y
. This will generate the router file for the default application route.
application.js
file in the app/router
folder:// app/router/application.js import Ember from 'ember'; export default Ember.Route.extend({ model() { return new Ember.RSVP.Promise(function(resolve) { resolve({message: 'Resolved'}); }); } , setupController(controller, model){ this._super(controller, model); console.log(model.message); } });
In the router, we created a new model. This model will be accessible to our application template. In this model, we are returning Ember.RSVP.Promise
, which is Ember's way of dealing with a promise. It can either resolve or reject. For the sake of simplicity, we are having it return a message.
The setupController
hook to set up the controller for the current route. As we are overwriting setupController
, it also overwrites its default behavior. Therefore, we must call super
on it. Otherwise it may effect how it normally behaves. We can use console.log
to output the model message to the console.
Asynchronous routing
During a transition, the model hook is fired in the router. If, during this transition, the model is returning an array, it will return immediately. On the other hand, if the model is returning a promise, it must wait for this promise to fulfill or reject. The router will consider any object with a then
method defined on it to be a promise. After the promise fulfills, the transition will continue from where it left off. It's possible to chain multiple promises, so the next promise or model must be fulfilled before the transition will be complete.
// app/routes/application.js import Ember from 'ember'; export default Ember.Route.extend({ model() { return Ember.RSVP.Promise.reject('error'); }, setupController(controller, model) this._super(controller, model); console.log(model.message); }, actions: { error(reason){ console.log(reason); } } });
In the preceding code, the model returns a rejected promise. As described in the Loading and error handling recipe, there is something called an error
event. This will fire only when an error occurs in the model. We can then log the error to the console.
application.hbs
file in the app/templates
folder:// app/templates/application.hbs {{outlet}} Message: {{model.message}}
If the promise doesn't reject, the model message will be displayed. If the model rejects, then nothing will be displayed; the route halts the loading and the console will show the message, error.
Error events bubble upwards. In this case, we are already on the application route and it can't bubble up any further. If we were in another route, we could have returned true and that error would have bubbled up to the application error event.
application.js
file again in the app/router
folder. Let's deal with the rejection:// app/routers/application.js import Ember from 'ember'; export default Ember.Route.extend({ model() { return new Ember.RSVP.Promise(function(resolve,reject) { reject('error'); }).then(null, function() { return {message: 'Returned from rejection}; }); }, setupController(controller, model){ this._super(controller, model); console.log(model.message); }, actions: { error(reason){ console.log(reason); } } });
In the preceding code, the RSVP
promise rejects. However, we then return the message anyway, by chaining another promise at the end. This way the transition won't halt and will continue.
ember server
and open a web page. You should see this message:This message shows Returned from rejection because we handled the promise reject callback and returned a message anyway.
Promises are a way for Ember to handle asynchronous logic. A promise is an object that represents an eventual value. The promise can either reject or fullfill, as in resolve a value. To retrieve the value or handle when it rejects, you can use the then method, which can accept two different callbacks. The first is for fulfillment and the second is for rejection. For example, you might use the rejection to retry or return different data.