Ember Best Practices

Part 6: The Run Loop

What is the Run Loop?

A work scheduler!

Think queues of functions that run in order

Ember runs this scheduler as new DOM events come into the system

and will schedule various parts of its lifecycle into specific queues

Without a Run Loop

Click this if the presentation is being a jerk and not showing embedded

Run Loop Example

The Future

The Timers Queue

Calls to run.later and run.next get put into a timers queue with an associated timestamp

[(timestamp, fn), (timestamp, fn) ... ]

Ember will periodically check this queue and see if any need running

If some functions are up for execution, Ember will create a new run loop and schedule them in its actions queue

Calling run.cancel pulls items out of this queue

See: https://github.com/eoinkelly/ember-runloop-handbook#a-note-about-future-work

The Queues

The sync queue contains binding synchronization jobs.

The actions queue is the general work queue and will typically contain scheduled tasks e.g. promises.

The routerTransitions queue contains transition jobs in the router.

The render queue contains jobs meant for rendering, these will typically update the DOM.

The afterRender queue contains jobs meant to be run after all previously scheduled render tasks are complete. This is often good for 3rd-party DOM manipulation libraries, that should only be run after an entire tree of DOM has been updated.

The destroy queue contains jobs to finish the teardown of objects other jobs have scheduled to destroy.

Auto Run

Your Wingman

Auto Run Pseudo-code


$('a').click(() => {
  // 1. autoruns do not change the execution of arbitrary code in a callback.
  //    This code is still run when this callback is executed and will not be
  //    scheduled on an autorun.
  console.log('Doing things...');

  Ember.run.schedule('actions', () => {
    // 2. schedule notices that there is no currently available run loop so it
    //    creates one. It schedules it to close and flush queues on the next
    //    turn of the JS event loop.
    if (!Ember.run.hasOpenRunLoop()) {
      Ember.run.start();
      nextTick(() => {
        Ember.run.end()
      }, 0);
    }

    // 3. There is now a run loop available so schedule adds its item to the
    //    given queue
    Ember.run.schedule('actions', () => {
      // Do more things
    });

  });

  // 4. This schedule sees the autorun created by schedule above as an available
  //    run loop and adds its item to the given queue.
  Ember.run.schedule('afterRender', () => {
    // Do yet more things
  });
});
          

Lifted From: https://guides.emberjs.com/v2.11.0/applications/run-loop/#toc_what-happens-if-i-forget-to-start-a-run-loop-in-an-async-handler

Tests and the Run Loop

Auto Run is disabled in tests :_(

Ember don't want you running outside its run loop. Tries to "help" you avoid async code running between tests

Any ember schedule done in an async function not started by Ember will fail your test

We still do it (lulz)

But...

Run loop still happens. Ember still wraps lifecycle calls on render.

From your test, wrap calls into your test with async side affects. Mainly a concern for unit tests.

From you code, you need to wrap the right things in run loops

Advice

Functions not in a run loop

Calls to setTimeout, setInterval (NOT EVEN ONCE)

jQuery event handlers (AVOID when there's an Ember Way)

jQuery ajax handlers (DON'T DO IT)

3rd party lib async handlers (lodash, clipboard, etc)

Wrap these callbacks in run loops plzzzzz, unless its a performance sensitive path

Examples where you shouldn't need to make your own run loops

  • lifecycle functions
  • computeds/observers
  • RSVP promises

References