Assertion failed: You have turned on testing mode, which disabled the run-loop’s autorun. You will need to wrap any code with asynchronous side-effects in an Ember.run
I would like to make it easier for people to understand what to do when they encounter this message by creating a guide that will explain what this message means and what they can do about it. Also, this guide will include description of the kinds of asynchronous calls that need to be wrapped in Ember.run
and which do not.
I understand this problem in the following way:
In normal operation, Ember applications run asynchronously. This means that the application doesn’t run as a predefined sequence of operations, but rather a dynamic series of events and a queue of operations for each event. This is referred to as the Ember RunLoop and manages the order of fired events(scheduling), eliminating unnecessary duplicate events(debouncing) and making sure that all operations are executed.
The RunLoop has 2 methods Ember.run.begin() and Ember.run.end(). Ember.run.begin()
causes the RunLoop to start listening for asynchronous calls. Ember.run.end()
starts the RunLoop. When started, the RunLoop will cycle through all of the events, executing all of the operations until the queues are depleted.
When testing, you want to run each test in isolation and after asynchronous operations are executed. To do this, Ember disables the RunLoop assuming that any code that has asynchronous side-effects will call start the RunLoop when necessary.
To manually start the run loop, wrap your code with Ember.run( /** your code **/ )
.
Ember.run(function(){
// all call that results in asynchronous operations goes here
});
// or
Ember.run( /** object **/, /** method name **/, /** arg1 **/, /** arg2 **/ );
WARNING If you’re not careful or when using 3rd party plugins, its possible to introduce an asynchronous side effect that is called after RunLoop finishes. If you do this, wrapping your code in Ember.run will still produce the assert warning.
For example, using setTimeout with a callback that relies on the RunLoop maybe produce assert warning.
/**
* BAD: may produce assert message because Em.Object.create() may run after RunLoop finishes
*/
var callbackWithAsyncSideEffect = function() {
return Em.Object.create();
}
Ember.run(function(){
setTimeout(callbackWithAsyncSideEffect, 3000);
});
You can eliminate this message by wrapping Em.Object.create() in Ember.run().
/**
* BETTER: assert message will not be produced but still using setTimeout
*/
var callbackWithAsyncSideEffect = function() {
var created;
Ember.run(function(){
created = Em.Object.create({});
});
return Em.Object.create({}); // this call requires RunLoop and will create an assert message
/**
* BETTERER: do it in one line without assert message but still using setTimeout
* return Ember.run( Em.Object, 'create', {} );
*/
}
Ember.run(function(){
setTimeout(callbackWithAsyncSideEffect, 3000);
});
BEST: Eliminate setTimeout and use Ember.run.scheduleOnce( /** action **/, /** object **/, /** method **/ )
to schedule the callback to execute at the appropriate moment in the RunLoop.
Ember.run.scheduleOnce('afterRender', this, 'callbackWithAsyncSideEffect');
I’m looking for a complete list of queues that can be used, if you have it, let me know.
What happens in production when your code has Ember.run and Ember executes the main RunLoop?
Nothing weird. The operations in your Ember.run will be merged into the main RunLoop allowing for normal operation.
How are promises affected?
If you use Ember.RSVP or a library that uses RSVP.js library to create a promise, you’ll have to wrap the creating code in Ember.run
.
What kind of operations have asynchronous side effects?
Em.Object.set()
Em.Object.create()
Em.Object.destroy()
Em.$.ajax()
( and related functions )
Examples
AJAX Requests
App.ProductsRoute = Ember.Route.extend({
model: function() {
var promise;
Ember.run(function(){
promise = Em.$.getJSON('products.json')
});
return promise;
}
});
Masonry Tiles
// source http://alexmatchneer.com/blog/ember_run_loop_talk/#/practical
App.MasonryView = Ember.CollectionView.extend({
didInsertElement: function() {
// At this point, no child elements have been rendered, so
// schedule buildMasonry to run after the child elements
// have rendered.
Ember.run.scheduleOnce('afterRender', this, 'buildMasonry');
},
buildMasonry: function() {
this.$().masonry();
}
});
What am I missing? What’s incorrect or could be clearer?