Ember Run Loop

Kuba Niechciał

Content

  1. What is the reason of using Ember Run Loop?
  2. What actually is Ember Run Loop?
  3. Ember Run Loop guts.
  4. Run, Run Loop, run - couple of examples.
  5. Taking Run Loop into your own hands.
  6. Autoruns and thier implications.
  7. More to read and watch.

How does Ember.js work in the browser?

Event driven environment afterwards

Short setup phase on DOMContentLoaded event

keyUp, keyDown, click, mouseMove and others...

What is the reason of Ember Run Loop?

Performance

Batch similar actions

Reduce number of expensive actions 

<div id="foo"></div>
<div id="bar"></div>
<div id="baz"></div>
foo.style.height = "500px" // write
foo.offsetHeight // read (recalculate style, layout, expensive!)

bar.style.height = "400px" // write
bar.offsetHeight // read (recalculate style, layout, expensive!)

baz.style.height = "200px" // write
baz.offsetHeight // read (recalculate style, layout, expensive!)
foo.style.height = "500px" // write
bar.style.height = "400px" // write
baz.style.height = "200px" // write

foo.offsetHeight // read (recalculate style, layout, expensive!)
bar.offsetHeight // read (fast since style and layout is already known)
baz.offsetHeight // read (fast since style and layout is already known)
{{user.firstName}}
{{user.fullName}}
user: Ember.Object.create({firstName:'Tom', lastName:'Huda'});
fullName: Ember.computed 'user.firstName', 'user.lastName', function() {
    this.get('user.firstName') + ' ' + this.get('user.lastName');
});

user.set('firstName', 'Yehuda');
// {{firstName}} and {{fullName}} are updated

user.set('lastName', 'Katz');
// {{lastName}} and {{fullName}} are updated

What is Ember Run Loop?

Well, it's not a loop...

Run Loop aggregates changes over time and schedules them

Than executes them in some moment that probably would be optimal

How Ember Run Loop is triggered in the framework?

Ember listens to set of events on the level of top app element (mostly <body>)

On any event, Ember starts Run Loop and starts batching actions to execute

At the end of the handler code, the queues are flushed and the job is done

Ember Run Loop is based on queues

Ember.run.queues
["sync", "actions", "routerTransitions", "render", "afterRender", "destroy"]

What is the algorithm of execution?

  1. Check for any jobs in queues - if none, exit.
  2. Get first queue by priority and set as current.
  3. Build temporary queue and move contents of current to temporary.
  4. Process temporary by order of injection order.
  5. Start the process again from most priority queue.

What is the reason of fifth step?

Most used methods of Ember Run

Schedulers

Wrappers

default actions queue

Other

private Ember API

Let's take a look at couple of examples

Noisy Run Loop

Nested Run Loops

Going back to former queues

Example of using #sync

App = Ember.Application.create();

App.IndexController = Ember.Controller.extend({
  actions: {
    handleChange: function() {
     // Ember.run.sync();
      console.log(this.get("value"));
    }
  },
  value: 0,
  classNames: ["controller"]
});

App.SomeValueComponent = Ember.Component.extend({
  valueChanged: function(val) {
    this.set('value', val);
    this.sendAction('valueChange');
  },
  didInsertElement: function() {
    this.$("input").on("keyup", Ember.run.bind(this, function() {
      this.valueChanged(this.$("input").val());
    }));
  },
  classNames: ["component"]
});

Doesn't apply data-down-action-up

Example of using #debounce

Sometimes we need to handle some repeated events - like scrolling

Observers vs. computed properties

Which is executed first in Run Loop?

partOfNameChanged: Ember.observer("firstName", "lastName", function() {
    console.log("[Observer]: Executing...");
})

fullName: Ember.computed("firstName", "lastName", function() {
    console.log("[Computed property]: Executing...");
    return (this.get("firstName") + " " + this.get("lastName"));
})

toggleName: function() {
    this.set("firstName", "Foo");
    this.set("lastName", "Bar");
}

How many times does it log?

partOfNameChanged: Ember.observer("firstName", "lastName", function() {
    Ember.run.once(this, "doSomeProcessing");
})

doSomeProcessing: function() {
    console.log("[Observer]: Executing...");
}

fullName: Ember.computed("firstName", "lastName", function() {
    console.log("[Computed property]: Executing...");
    return (this.get("firstName") + " " + this.get("lastName"));
})

toggleName: function() {
    this.set("firstName", "Foo");
    this.set("lastName", "Bar");
}

How many times does it log now?

When must I explicitly start

Ember Run Loop?

Ember Run Loop should be started explicitly in asynchronous callback (AJAX, custom event handlers, etc.)

this._select = this.$().select2(options);

// run ember bindings on after select2 `change` event
this._select.on("change", run.bind(this, function() {
  var data = this._select.select2("data");
  this.selectionChanged(data);
}));

Can I schedule jobs if Ember Run is not running?

$('a').click(function() {
  console.log('Doing things...');

  Ember.run.schedule('actions', this, function() {
    // Do more things
  });
});

Yes, I can, thanks to autoRun

Auto start Ember Run when scheduling

But... autoRun is switched off on testing

Testing with Ember Run Loop

  • Autoruns are Embers way of not punishing you in production if you forget to open a runloop before you schedule callbacks on it.

  • Some of Ember's test helpers are promises that wait for the run loop to empty before resolving. If your application has code that runs outside a runloop, these will resolve too early and give erroneous test failures which are difficult to find. Disabling autoruns help you identify these scenarios.

More to read and watch

Thanks guys!

Ember Run Loop

By Kuba Niechcial

Ember Run Loop

Presentation about Ember Run Loop - what it is, how it works and how to use it by @jniechcial at @netguru.

  • 1,022