How to stop using deferReadiness and advanceReadiness in Ember
Summary
Several times I have read comments from @tomdale trying to explain to people why one should avoud deferReadiness of the application inside initializers of an application. I did use it until just a couple of days ago when I finally understood how to implement at better way of reaching a set application state before showing our application to the customer.
Problem
When building a single-page application and it’s loaded for the users you most probably want to do stuff before actually serving the application to the user. This could be things like:
- Setting up a websocket
- Authenticating the user
- Preloading a set of data
Your first bet would be to ask yourself the question “How do I stop loading the application until I do X?” and after that you find the deferReadiness and advanceReadiness. It does not take that long until you actually implement an initializer with the above functionality in something like this:
/**
Session Service Initializer
@class initializers.sessionService
@extends Ember.Initializer
*/
export function initialize(container, application) {
// Wait until all of the following promises are resolved
application.deferReadiness();
// Get session-service
var session = container.lookup('service:session');
// Find the current customer, use promise to reflect to other controllers
if (session.hasSession()) {
// Get the store
var store = container.lookup('store:main');
// Fetch the user
store.find('Customer', session.get('userId')).then(function(customer) {
// Set customer to application controller
session.set('user', customer);
// preload the application
session.preloadApplication();
// Continue the Application boot process, allowing other Initializers to run
application.advanceReadiness();
}, function() {
// Kill sesion object
session.set('user', '');
session.set('authToken', '');
// Continue the Application boot process, allowing other Initializers to run
application.advanceReadiness();
});
} else {
// Continue the Application boot process, allowing other Initializers to run
application.advanceReadiness();
}
}
Yaay! Success! Problem is if you ever forget to advanceReadiness, if you do it more than once or if your application breaks somewhere in between - which will lead to very unexpected behaviors. @tomdale wrote “asynchronous instance initializers are a very bad idea (because they don’t participate in the router promise chain) “ which makes a valid point.
So how do you solve this issue?
Use the beforeModel() hook in the route, with a returned promise on all the things you want to preload!
First I moved the setups I wanted into my service, returning a promise.
import Ember from 'ember';
export default Ember.Service.extend({
/**
* This is run from the route after the applciation has loaded
* @return {[type]} [description]
*/
setup: function() {
return new Ember.RSVP.Promise(function(resolve, reject) {
// Do something that either resolves or reject
if (true) {
resolve();
} else {
reject();
}
});
}
});
import Ember from 'ember';
export default Ember.Service.extend({
/**
* This is run from the route after the applciation has loaded
* @return {[type]} [description]
*/
setup: function() {
return new Ember.RSVP.Promise(function(resolve, reject) {
// Do something that either resolves or reject
if (true) {
resolve();
} else {
reject();
}
});
}
});
Then I simply made sure that these where triggered in my application beforeModel.
import Ember from 'ember';
export default Ember.Route.extend({
/**
* Before anything proceeds in the application, resolve these promises
* @return {[type]} [description]
*/
beforeModel: function() {
return Ember.RSVP.all([this.get('sessionService').setup(), this.get('cordovaService').setup()]);
},
This way your application will always load and your application-template will render letting your users see something else but a blank broken page, and still preload the things you need done before your model is triggered.