A Brief History of Time: The Ember Run Loop in short

What is the Ember run loop and how can I use it? I needed my time to understand what the Ember run loop does and how to work with it. Partly because its name is very confusing… There is still a lot of room for me to dig deeper but I felt like writing a short article about “the run loop” and give some usage examples for those who are completely new to the subject.

What does the run loop do?

The run loop is used to batch and order the execution of tasks. Through ordering and reordering, the tasks can be executed in a way that is most efficient. This is essential for Ember to run effectively, for example to batch DOM updates, which are always costly.

The batching is done in six different queues, with each one having its own responsibility:

  • actions: the general work queue, contains scheduled tasks like promises
  • render: rendering, DOM updates
  • afterRender: tasks that are executed after all primary render tasks; good for all 3rd-party-library DOM manipulation work
  • routerTransitions: the router’s transition jobs
  • sync: binding & synchronization jobs
  • destroy: jobs for finishing teardown of objects

Contrary to how its name run loop sounds, the run loop is actually not running at all times. Neither does one run loop exist as a singleton. Run loops are started or fired each time either in response to a user’s action or a timer event. When all tasks in one run loop are done, the current run loop finishes and control is returned to the system.

When a user or a timer event triggers the execution of some code, Ember works its way through the code and collects all tasks in the above mentioned set of queues. All tasks which are part of the code are registered as jobs on the queues. Jobs are basically just javascript (callback) functions. When Ember gets to the end of a piece of code that is executed in response to an event, the run loop for this event is closed and Ember starts executing those jobs one after one. Only those jobs themselves can then add more jobs on the queues at that point, while it is closed to any other code. If a new event triggers the execution of the next piece of code, a new run loop collects those jobs and is executed after the current run loop has finished.

How can I make use of this?

We can actively manipulate the run loop using the methods provided by the Ember.run namespace (Ember.run itself can also be called as a function additionally to its purpose as a namespace, which I find rather confusing). Here are some use cases I collected throughout the last months where the manipulation and/or control of the run loop was useful or necessary.

Ember.run.later

If your app is written in Ember and you want to schedule an event after a certain time interval in the future, you should not use the window.setTimeout function. Use Ember.run.later instead, that way the execution lies in the hand of Ember and events to be scheduled at the same time can actually be executed in the same run loop.

Ember.run.later(context, function() {
  // code here will execute within a run loop in about 500ms
}, 500);

Ember.run.scheduleOnce

As mentioned above, if you do DOM manipulation via a 3rd party library like jQuery, you should make use of the afterRender queue.

Ember.run.scheduleOnce('afterRender', this, function(){
  // jQuery or other 3rd-party-library DOM manipulation logic goes here
});

For details on this, also check out this blog post.

Ember.run.bind

Used to correctly integrate 3rd-party-library callbacks into Ember. That way event handlers can be wrapped and batched in run loops, which makes their execution more efficient:

jQuery(window).on('resize', Ember.run.bind(this, this.handleResize));

... is a short version for:

var that = this;
jQuery(window).on('resize', function(){
  Ember.run(function(){
    that.handleResize();
  });
});

Ember.run.once

When observing the @each property of a collection I noticed that on loading the app, the observing function was executed each time one item of the collection was loaded. This seems like too much extra work since one execution when all the items are loaded should be sufficient when the app is started. Also I noticed that the execution had a weird "off by one" problem, meaning that the observing function would be executed the first time when the first item of the collection was actually "not filled with data" yet. This results in the problem that the last execution of the observer function, triggered when the last collection item is supposed to have loaded, is also fired when the data of that item is not available yet... So for example when you want to update a view with the data of a collection and you need to do some calculations in order to do that, the last item is left out, because the data was not available at the moment when the observing function was fired. Admittedly the problem may lie deeper within my Ember app and I don't know if this is a general problem, but...

The below pattern batches the execution of the observer function, so that it only gets executed once within one loop. This reduces the amount of times the function is executed when first loading the data of a collection, although it does not guarantee that it only gets executed once. But the biggest optimization is that it also resolves my "off by one" problem:

_updateMyView: function () {
  // perform updates here...
},

updateMyView: function () {
  Ember.run.once(this, '_updateMyView');
}.observes('model.myCollection.@each'),

To gain a quick overview over what all the methods offered by the Ember.run namespace do, I recommend taking a look at this high level overview.

Ember run loop and tests

This is an important subject. Technically you should not have to call any Ember.run methods directly from your tests. In practice, this may be different. You should be well aware of the side effects you can cause though when doing so (for example calling Ember.run.later will break the andThen method and using run.schedule, run.scheduleOnce or run.once at a time when currently no run loop is running will throw an error...).

On asynchronous side-effects in testing also check out this guide.

The End

That's it already. I recommend taking a look at the Ember run loop FAQs. If you want to do some further reading, check out the Ember runloop handbook and my blog post about the implementation of the Ember Run Loop.