In the previous post we saw why the Ember Run Loop is important and what limitations are imposed by the browser on that mechanism. Now, I’ll answer the most important question in this post - what is Ember run loop?
In the previous post we saw why the Ember Run Loop is important and what limitations are imposed by the browser on that mechanism. I showed you some simple, but unreal examples of the situations where batching can improve the code performance. Welcome to the next part!
Now, I'll answer the most important question in this post - what is Ember run loop? Well, it’s not a loop in the sense of the common for-loop or while-loop. Rather, it’s a mechanism that batches assorted actions (like setting, actions, or transitions) and then decides to execute them in some planned order.
So, when does the run loop start batching? As I said earlier, Ember is a fully event-driven framework. Everything that happens is a reaction to user behaviour. The user has clicked something, has moved the mouse over the page or pressed some key. Each time the lowest level Ember handler starts handling the event, a run loop is created and starts accepting jobs.
From the low-level handler, the stack of execution runs up through all the abstraction layers of Ember, eventually reaching the code you have written yourself. We can, for example, set some attributes and perform a transition using the
transitionToRoute methods. The implementation of these methods uses the run loop by scheduling synchronization of bindings used by these attributes and the transition. Basically, nearly everything we do using the Ember API is scheduled into the run loop and left to execute in the future. After the stack of execution gets back to the low level handler, the run loop is closed.
Ok, so when is the run loop executed? Right after it is closed, in the same event handler. If you remember what I said earlier about the event environment in browsers, you’ll recall that the run loop can execute at most every one millisecond. This is an important fact that we will return to later. It also means that Ember’s reaction to user behaviour is fully enclosed in the run loop and is executed before the next event arises.
Next question - how are these jobs executed? The internals of the Ember run loop consist of six different queues that are ready to accept jobs. Each is responsible for a different kind of job:
syncfor synchronization of bindings (e.g. between controller and components in the template)
actionfor handling actions and promises
renderfor rendering templates
afterRenderfor any job that must be performed after rendering
destroyfor handling garbage
I’ve listed them in the order of execution. However, this order is not so straightforward. After you sync the bindings in the first queue, it is very likely that your action code will generate new bindings that will be batched to the run loop sync queue and thus skip the execution. If the algorithm simply executes the queues in the order they are listed, until all of them are empty, we won’t get the performance improvement that we want - rendering will still happen multiple times.
Therefore, the algorithm for execution returns to the first queue after flushing each of them and checks for any new jobs. Of course, there still might be some jobs in the
destroy queues that will break the concept and add some bindings, leading to rerendering the DOM again in the same run loop. But this is very uncommon and is mostly the result of bad code or intended behaviour - we will return to this later.
The Ember run loop provides a very interesting API which can give you full control over your code execution. Firstly, I will cover some of the most common methods used from the Ember Run namespace. I’ve intentionally divided them into two blocks - schedulers and wrappers.
The schedulers provide the ability to schedule passed functions in the existing run loop, while the wrappers wrap the passed function in a completely new instance of Ember run loop. Yup, that’s not a typo - run loop is not a singleton and Ember can have multiple run loop instances at once. However, each opened instance blocks the execution of its parent until all of its queues are flushed (examples coming up soon!).
This post was quite hard for people relatively new to Ember. I talked a lot about the Ember Run Loop guts, but I hope that it was a good intro before the sea of examples you will see in the next part. I’ll show you some cases where the Ember Run Loop is a great thing and where it is hard to figure out what is going on. Till the next part!
If - for any reason - you haven't read the first part of this four-post series, Ember Run Loop intro is here for you! Also, feel free to browse through our list of resources to master Ember.js and learn more.