Introduction
Ember.js is an ambitious Javascript MVC framework. It not only provides a clear way to build a web application using router, controller and view. but also utilizes a clever structure, the run loop, to schedule the jobs in an Ember application so as to avoid unnecessary computations.
The run loop is entirely wrapped in the implementation of Ember. So developers building light web applications don’t need to worry about explicitly invoking it. Nevertheless, it is worthwhile for us to take a deep look at its implementation.
Basic Idea
Ember.js arranges jobs into six different queues:
1
sync
1
actions
1
routerTransitions
1
render
1
afterRender
1
destroy
sync
Synchronization jobs. e.g. all the bindings in Ember.js.
actions
A general work queue. Typically contains scheduled jobs.
routerTransitions
Contains router transition jobs.
render
Contains rendering jobs, which typically updates DOM. e.g. template rendering.
afterRender
Contains scheduled jobs that should be triggered after the render jobs finish.
destroy
Contains jobs to destroy objects.
The priority of these queues is first to last, from
to 1
sync
.1
destroy
The algorithm
In a run loop, Ember.js will look at each queue in the order of priority. If a queue has pending tasks, the run loop will execute them. Obviously, this will possibly schedule more tasks. So the run loop continues to execute the tasks in the above order until there is no pending jobs.
Advantages
The Ember run loop separates logics and rendering so that it avoids running many unnecessary codes, e.g. updates the bindings.
To illustrate this, let me use an example from the official website.
We have an object,
,1
User
a simple template,
and a few Javascript code.
Without the run loop, the above code should ask the browser to render the template three times:
But with the run loop, the rendering jobs in the
queue won’t be executed before all the above code is done:1
render
Implementation
Code structure
is where the run loop code lies. It provides all the API methods developers need to know. Moreover, it adds1
ember-metal/lib/run_loop.js
,1
sync
and1
actions
queues.1
destroy
is the underlying actual run loop handler used by1
ember-metal/lib/backburner.js
.1
run_loop.js
adds1
ember-views/lib/ext.js
and1
render
queues.1
afterRender
adds the1
ember-routing/lib/ext/run_loop.js
queue.1
routerTransitions
Backburner structure
provides a class of run loop controller, 1
backburner.js
. Each 1
Backburner
handles one or more similar 1
Backburner
objects, aka the run loops. Moreover, each 1
DeferredActionQueues
is consisted of 1
DeferredActionQueues
s, which is the place the scheduled jobs rest.1
queue
In conclusion, the
starts 1
Backburner
to run different jobs. Each 1
DeferredActionQueues
executed the jobs in a specific priority order by sending them to different 1
DeferredActionQueues
s.1
queue
Actual Code
-
Initiating the actual handler,
.1
backburner
1
run_loop.js
1
backburner.js
In
and 1
backburner.begin
, we can see that 1
backburner.end
is used as a stack of run loops. Together with the 1
instanceStack
and 1
onBegin
functions provided in 1
onEnd
, it is able to handle run loops that are invoked during the execution of another run loop.1
run_loop.js
Note that
here is specified as 1
defaultQueue
. If we don’t explicitly set 1
actions
, 1
defaultQueue
will be chosen as 1
queueNames[0]
. 1
defaultQueue
is the queue where jobs are scheduled into if no target queue is specified.1
defaultQueue
-
1
Ember.run
1
run_loop.js
1
backburner.js
Asks
to initiate a new run loop to run the job in arguments. It wraps the execution in 1
backburner
and 1
begin
calls so that the 1
end
call ensures that bindings are updated and events are responded.1
flush
-
1
Ember.run.join
1
run_loop.js
Works almost the same as
. However, if there already exists a run loop, instead of initiating a new one, 1
Ember.run
will schedule the job into the existing run loop in the 1
Ember.run.join
queue.1
actions
-
1
Ember.run.bind
1
run_loop.js
Provides a callback method for third party modules to use.
-
and1
Ember.run.begin
1
Ember.run.end
1
run_loop.js
Directly asks backburner to start or end the current run loop.
-
,1
Ember.run.schedule
and1
Ember.run.scheduleOnce
1
Ember.run.once
1
run_loop.js
1
backburner.js
All these three methods schedule jobs to the current run loop. The different is that
ensures that the job is only executed once and that 1
scheduleOnce
is the convenient method for 1
once
on the 1
scheduleOnce
queue. All of them invoke 1
actions
, which asks the current run loop, 1
backburner.schedule
to push the job into corresponding queue.1
currentInstance
-
,1
Ember.run.later
,1
Ember.run.next
,1
Ember.run.hasScheduledTimers
and1
Ember.run.cancel
1
Ember.run.cancelTimers
1
run_loop.js
1
backburner.js
This set of functions, together with
and 1
Ember.run.throttle
below enables developers to schedule delayed jobs. These functions maintain an array, 1
Ember.run.debounce
, to handle the delayed jobs. 1
timers
is an array of 1
timers
pairs.1
(executeAt, fn)
finds the place in1
searchTimer
where a new delayed job should be inserted.1
timers
runs all the delayed jobs that should be executed up till now.1
executeTimers
uses the native1
updateLaterTimer
function to schedule1
setTimeOut
at the time when next delayed job should be executed. (Note: I think there should be a1
executeTimers
here, but I’m not sure of this.)1
clearTimeout
returns true if and only if there are delayed jobs pending.1
hasScheduledTimers
With these three functions,
uses1
Ember.run.later
to place the job in1
searchTimer
array and calls1
timers
to update the next1
updateLaterTimer
call.1
executeTimers
schedules a job 1ms later. What it actually does is asking Ember to start a new run loop of the job right after the current one.1
Ember.run.next
returns the value of1
Ember.run.hasScheduledTimers
.1
hasScheduledTimers
cancels a specific timer by removing it from1
Ember.run.cancel
and other relative collections.1
timers
-
cancels all the pending timers by clearing the1
Ember.run.cancelTimers
array and other relative attributes.1
timers
-
1
Ember.run.sync
1
run_loop.js
1
backburner.js
is actually the only method that uses 1
Ember.run.sync
on a specific queue. The Ember developer team is trying to figure out a way to replace it with other functions. Nevertheless, it is still helpful since there indeed are places we need to synchronize the bindings.1
flush
-
and1
Ember.run.debounce
1
Ember.run.throttle
1
run_loop.js
1
backburner.js
#### What do they do?
ensures a job is never executed within a specific time interval.1
Ember.run.debounce
ensures a job is never executed more frequently than a specific time interval.1
Ember.run.throttle
#### How do they do it?
The functions use
and 1
_debouncees
to maintain the list of jobs that are using these functions. They also use the native 1
_throttlers
to schedule of calls.1
setTimeOut
Note:
can also use return values of these functions to cancel debounce and throttle timers.1
Ember.run.cancel
-
1
Ember.run._addQueue
1
run_loop.js
Adds a new queue,
. All jobs of this queue is executed after 1
name
. 1
after
and 1
ember-routing
use this method to add 1
ember-views
, 1
render
and 1
afterRender
queue.1
routerTransitions
How is the run loop used?
Using the run loop is amazingly simple. For instance, in
,1
ember-views/lib/views/view.js
uses the 1
replaceIn
method to schedule a replace in the 1
Ember.run.scheduleOnce
queue.1
render
When does Ember.js start a run loop?
As we have seen in the above code, Ember.js uses the run loop everywhere. But besides these run loop calls from inside Ember.js, when does Ember.js start a run loop?
To answer this question, let’s take a look at this piece of code from
,1
ember-views/lib/system/event_dispatcher.js
Notice the
calls in these functions. Every event sent to Ember.js initiates a run loop. The event handle will then possibly trigger some binding jobs and render jobs. After all the jobs are down (no pending jobs), the event is successfully handled and the run loop is completed.1
Ember.run
Conclusion
The run loop is a smart idea to avoid unneeded computations, which is largely favorable in web app development. Also, Ember.js provides a detailed API for the developers to fully take advantage of the run loop structure.