Backburner.js – A Dive into the Implementation of the Ember Run Loop

Recently in my Ember project I was stuck with the same bug for quite a while. The stack told me that the error was occurring somewhere in a module called Backburner. I had seen this namespace before in the stack and always wondered what it actually was. After doing my research it turned out that this is the namespace which contains the implementation of the Ember Run Loop. The backburner.js module is the Ember Run Loop in form of a generic library that can also be used as a plugin in non-Ember projects.

I decided to take a closer look at the implementation of Backburner, so I could better understand my error stack. The code is not very thoroughly commented, but on the other hand very readable. Documenting my findings here will help me gain a deeper understanding of the Run Loop and I hope it’ll help somebody else, too.

Overview

So let’s dive into the Backburner code first with a diagram to get an overview over the most important namespaces and functions.

backburner_internals

Here I tried to display the methods calling each other as a flow diagram, which does not exactly match reality but makes it easier to understand. Also you’ll notice that I left out most methods that are being used to add actions to the queue(s) for simplicity. Instead I’m focusing here on the execution of the queued actions.

The Backburner namespace

When you call Ember.run the first thing which is called inside Backburner is Backburner.run. This method fires a new Run Loop. It then calls the begin method which instantiates a new set of Queues in the form of a new DeferredActionQueues object. After that setup work is done, control is given back to the run method which after checking for errors promptly calls the end method, which then triggers the flush method of the DeferredActionQueues object.

The DeferredActionQueues namespace

As said before a DeferredActionQueues object holds one set of queues. Now as we know in Ember there are six queues: actions, render, afterRender, routerTransitions, sync and destroy, which are executed one after another. When Backburner is used as a plugin in other applications, of course it can be instantiated with different queues as well.

The DeferredActionQueues.flush method, which is called by the Backburner namespace when the set of queues is meant to be executed, iterates over all queues and calls again the flush method of each queue.

The Queue namespace

A Queue object represents a single queue. This queue contains an array with the actions to be executed. When DeferredActionQueues iterates over the queues in its flush method, it calls the respective flush methods of each queue. This method then iterates over all the actions stored in the queue and invokes them one after another using the invoke method of the namespace.

Conclusion

One addition to make is that Backburner does not only ever contain one set of queues, but can as well have several instances of DeferredActionQueues on the stack. In that case the sets of queues will be executed one after another.

As we see the whole architecture is not very complicated to understand: We have a stack of sets of queues at the top level. When execution is triggered it goes one level down and the set of queues flushes its own queues one after another. Then at the level of a single queue all actions are executed one after another.

That’s it for the most important parts. Let me know if you think I forgot something important. For a full overview over the Run Loop also check out my other blog post on this topic.

Appendix: Logging RSVP errors

This is kind of unrelated to the general topic of this blog post, but I found out later that my bug was triggered by a failing ajax request which was wrapped via ic-ajax in an RSVP promise and executed in a Run Loop. So the whole thing was less mysterious than expected. The first step towards finding out about this was to add the following to my application code:

Ember.RSVP.on('error', function(reason) {
  console.assert(false, reason);
});

Only if you add this to your app.js or to an initializer errors occurring in RSVP promises will be logged in the console and you can inspect the full stack. That was my first gotcha and if you have errors involving promises and the Run Loop, maybe this will make life easier for you, too.