当前位置: 首页 > 软件库 > 程序开发 > >

ember-runloop-handbook

A deep dive into the Ember JS runloop.
授权协议 Readme
开发语言 JavaScript
所属分类 程序开发
软件类型 开源软件
地区 不详
投 递 者 冀鸿才
操作系统 跨平台
开源组织
适用人群 未知
 软件概览

Ember runloop handbook

by Eoin Kelly

Creative Commons License

Table of contents

Contributing

If you spot any of the (sadly inevitable) errors you would be doing me a greatfavour by opening an issue :-).

Introduction

You can get started with Ember application development without understanding therunloop. However at some point you will want to dig in and understand itproperly so you can use it skillfully. It is my sincere hope that this handbookcan be your guide.

We are about to take a deep dive into the Ember.js runloop. Together we willanswer these questions:

  1. Why does Ember have this runloop thing?
  2. What is the runloop?
  3. How can we use it skillfully?

This is not reference documentation - the Ember APIdocs have that nicely covered. This isn't even the"I'm an experienced dev, just give me the concepts in a succinct way"documentation - the Official Ember Run-loopguide has thatcovered. This is a longer, more detailed look at the runloop.

Naming is hard

As you learn more about the Ember runloop you will come to understand that,well, it just isn't very loopish. The name is a bit unfortunate as itimplies that there is a single instance of the runloop sitting somewhere inmemory looping endlessly running things. As we will see soon this is nottrue.

In alternate universes the runloop might have been named:

  • Ember Work Queues
  • Ember Coordinated Work Algorithm
  • Ember Job Scheduler
  • Runelope (a large friendly creature that lives in your Javascript VM andmanages the work Ember does in response to events)

OK some of those names are really terrible (except Runelope of course, thatone is pure gold and should be immediately pushed to Ember master). Naming is ahard problem and hindsight is 20/20. The runloop is what we have so that iswhat we will call it but try not to infer too much about its action from itsname.

Why do we have a runloop?

On our journey to understand the runloop we must first understand theenvironment it lives in and the problems it is trying to solve. Lets set thescene by refreshing a few fundamentals about how Javascript runs. (If you are anexperienced Javascript developer you may want to just skip this part)

Our story begins with when the browser sends a request to the server and theserver sends HTML back as a response.

The browser then parses this HTML response. Every time it finds a script it executes itimmediately(*) Lets call this the setup phase. This setup phase happenswell before the user sees any content or gets a chance to interact with the DOM.Once a script is finished executing the browser never runs it again.

(*) Things like defer tweak this somewhat but this is a useful simplification.

The browser does most of its communication with Javascript by sending "events".Usually these are created in response to some action from one of:

  1. The user e.g. moves their mouse (mousemove)
  2. The network e.g. an asset has been loaded on the page (load)
  3. Internal timers e.g. a particular timer has completed

However there are a few events that the browser generates itself to tellJavascript about some important event in the lifecycle of the page. The mostwidely used of these is DOMContentLoaded which tells Javascript that the HTMLhas been fully parsed and the DOM (the memory structure the browser builds byparsing the HTML) is complete. This is significant for Javascript because itdoes most of its setup work in response to this event.

Javascript is lazy but well prepared! During the setup phase, Javascript prepared its workspace (or mise en place if you prefer) - it created the objects it would now needto respond to orders (events) from the browser and also told the browserin detail what events it cares about e.g.

Hey browser, wake me up and run this function I'm giving you whenever the userclicks on an element with an id attribute of do-stuff.

The description above makes it look like the browser is the one giving all theorders but the browser is a team player and has a few things it can do to helpJavascript get the job done:

  1. Timers. Javascript can use the browser like an alarm clock:

    Javascript: Hey browser, wake me up and run this function I'm giving you in 5 seconds please.

  2. Talking to other systems. If Javascript needs to send or receive data to othercomputers it asks the browser to do it:

    Javascript: Hey browser, I want to get whatever data is athttp://foo.com/things.json please.

    Browser: Sure thing but it might take a while. What do you want me to dowhen it comes back?

    Javascript: I have two functions ready to go (one for a successful data fetch andone for a failure) so just wake me up and run the appropriate one when youfinish.

    Browser: cool.

We usually refer to this talking to other systems stuff as Web APIs e.g.

  • XHR (AJAX) requests
  • Web workers
  • etc.

Javascript can use these services of the browser both during the setup phaseand afterwards. For example part of the Javascript response to a "click" event on acertain element might be to retrieve some data from the network and alsoschedule a timer to do some future work.

We now know enough to see the pattern of how javascript and the browserinteract and to understand the two phases:

  1. In the short setup phase the browser runs each script it finds on the pagefrom start to finish. Javascript uses this as time to do some preparation for next phase.
  2. Javascript spends most of its life responding to events. Many eventscome from the user but Javascript can also schedule events for itself by usingthe many services (web APIs) that the browser provides.

A solid understanding of this stuff is required to understand the runloop so ifyou are unclear about any of this and want to dig a little deeper I recommend awonderful video by Philip Roberts at JSConf EUthat goes into the Javascript event loop in more detail. It is a short watch and includesa few "aha!"-inducing diagrams.

Enter the Ember!

Things we already know

Since Ember is Javascript we already know quite a bit about how Ember works:

  • Apart from when the code is first found, all Ember framework and applicationcode is run in response to "events" from the browser.
  • The DOMContentLoaded event is significant in the life of an Ember app. It tellsEmber that it now has a full DOM to play with. Ember will do most of its "setup work"(registering for event listeners etc.) in response to this event.
  • Your Ember app can schedule its own events by asking the browser to do some workon its behalf (e.g. AJAX requests) or simply by asking the browser to be itsalarm clock (e.g. setTimeout)

Where does the framework end and my app begin

How does your Ember application relate to the Ember framework? The machineryfor responding to events is part of Ember framework itself but it does nothave a meaningful response without your application code.

For example if the user is on /#/blog/posts and clicks a link to go to/#/authors/shelly the Ember framework will receive the click event but itwon't be able to do anything meaningful with it without:

  1. A Router map to tell it how to understand the URL
  2. The Route objects themselves e.g. BlogRoute, PostsRoute, AuthorsRoute
  3. The models, controllers, views that all play a part in putting new data on the screen

What events does Ember listen to?

The Ember docs have a list of events Ember listens for bydefault which I have repeated here:

  1. touchStart
  2. touchMove
  3. touchEnd
  4. touchCancel
  5. keyDown
  6. keyUp
  7. keyPress
  8. mouseDown
  9. mouseUp
  10. contextMenu
  11. click
  12. doubleClick
  13. mouseMove
  14. mouseEnter
  15. mouseLeave
  16. submit
  17. change
  18. focusIn
  19. focusOut
  20. input
  21. dragStart
  22. drag
  23. dragEnter
  24. dragLeave
  25. dragOver
  26. dragEnd
  27. drop

These are the entry points into our code. Whenever Ember code runs after thesetup phase, it is in response to an event from this list.

How ember listens for events

This is a good resource forrefreshing your understanding of how DOM events work. To get the most of thefollowing discussion you should be familiar with how the browser propagatesevents and what the phrases "capturing phase" and "bubbling phase" mean.

Ember registers listeners for these events similarly to how we might do itourselves with jQuery i.e.

  • Ember attaches all its listeners to a single element in the DOM.
  • This element is usually <body>. If you specify a rootElement then that will be used instead.
  • Ember attaches its listeners to the "bubbling" phase.

Example: A simplistic approach

The pattern of how Javascript (Ember) works is periods of intense activity inresponse to some event followed by idleness until the next event happens. Letsdig a little deeper into these periods of intense activity.

We already know that the first code to get run in response to an event is thelistener function that Ember registered with the browser. What happens afterthat?

Lets consider some code from an imaginary simple Javascript app:

http://jsbin.com/diyuj/5/edit?html,js,console,output

This code manages the "Mark all completed" button in the UI.

Click the button a few times and notice the console output. Notice that thereare some patterns to the tasks performed:

  1. Updating the model
  2. Updating the DOM (rendering)

and that the do work as you find it approach that this app takes causes thesedifferent types of work to be interleaved.

The code in this app is obviously very incomplete and I'm sure you can see manyways it could be improved. However there are some problems that might not beobvious at first, problems that you will only start to notice when the appgrows in complexity. To understand these lets look at what it is not doing:

  1. It is not coordinating its access of the DOM. Every time the app updatesthe DOM the browser does a layout and paint. These are very expensiveoperations especially on mobile devices.
  2. It has no way of telling us when DOM updating is finished. We can certainlyhook into the click handler for the "Mark all completed" button but what ifhad started some asynchronous work like updating the server? If this app wasmore realistic it would be very difficult to know where we should add codethat would be run when all DOM updates had finished.
  3. It is not controlling when objects get deleted. Currently our app is sotrivial that this is not a problem but imagine if we had hundreds of todoitems and complex processing of each one i.e. processing each todo itemcreated a lot of temporary objects in memory. After a while the browserwill decide that enough is enough and that it needs to "clean up" theseobjects and make their memory available again i.e. it will run garbagecollection. Since our app cannot run while GC is happening the user maynotice a pause.

Together these problems mean our simplistic Todo app will have serious scaling problems.

Enter the runloop

We have identified some problems caused by an uncoordinated approach to doingwork. How does Ember solve them?

Instead of doing work as it finds it, Ember schedules the work on an internalset of queues. By default Ember has six queues:

console.log(Ember.run.queues);
// ["sync", "actions", "routerTransitions", "render", "afterRender", "destroy"]

Each queue corresponds to a "phase of work" identified by the Ember core team.This set of queues and the code that manages them is the Ember runloop.

You can see a summary of the purpose of each queue in the RunloopGuidebut here we are going to focus on the queues themselves.

How it works

First lets get some terminology sorted:

  • A job on a queue is just a plain ol' Javascript callback function.
  • Running a job is simply executing that function.

How Ember handles events:

  1. A browser event happens and Ember's registered listener for that event is triggered.
  2. Early on in its response to the event, Ember opens a set of queues and startsaccepting jobs.
  3. As Ember works its way through your application code, it continues toschedule jobs on the queues.
  4. Near the end of its response to the event Ember closes the queue-set and startsrunning jobs on the queues. Scheduled jobs can themselves still add jobs to the queues eventhough we have closed them to other code.
  5. The Runloop Guidehas an excellent visualisation of how jobs are run but in brief:
    1. Scan the queues array, starting at the first until you find a job. Finish if all queues are empty.
    2. Run the job (aka execute the callback function)
    3. Go to step 1

Lets consider some subtle consequences of this simple algorithm:

  • Ember does a full queue scan after each job - it does not attempt to finisha full queue before checking for earlier work.

  • Ember will only get to jobs on a queue if all the previous queues are empty.

  • Ember cannot guarantee that, for example, all sync queue tasks will becomplete before any actions tasks are attempted because jobs on any queueafter sync might add jobs to the sync queue. Ember will however do itsbest to do work in the desired order. It is not practical for your app toschedule all work before any is performed so this flexibility is necessary.

  • At first glance it may seem that the runloop has two distinct phases

    1. Schedule work
    2. Perform the work

    but this is subtly incorrect. Functions that have been scheduled on a runloop queuecan themselves schedule functions on any queue in the same runloop. It istrue that once the runloop starts executing the queues that code outside thequeues cannot schedule new jobs. In a sense the initial set of jobs that arescheduled are a "starter set" of work and Ember commits to doing it and alsodoing any jobs that result from those jobs - Ember is a pretty greatemployee to have working for you!

Something that is not obvious from that description is that there is no"singleton" runloop. This is confusing because documentation (including thisguide) uses the phrase "the runloop" to refer to the whole system but it isimportant to note that there is not a single instance of the runloop in memory(unlike the Ember containerwhich is a singleton). There is no "the" runloop, instead there can be multipleinstances of "a" runloop. It is true that Ember will usually only create onerunloop per DOM event but this is not always the case. For example:

  • When you use Ember.run (see below) you will be creating your ownrunloop that may go through its full lifecycle while the runloop that Emberuses is still accepting jobs.
  • Usually an Ember application will boot within a single runloop but if youenable the Ember Inspector then many more runloops happen at boot time.

Another consequence of the runloop not being a singleton is that it does notfunction as a "global gateway" to DOM access for the Ember app. It is notcorrect to say that the runloop is the "gatekeeper" to all DOM access in Ember,rather that "coordinated DOM access" is a pleasant (and deliberate!) side-effectof organising all the work done in response to an event.. As mentioned above,multiple runloops can exist simultaneously so there is no guarantee that allDOM access will happen at one time.

How often do runloops happen?

From what I have observed, Ember typically runs one runloop in response to eachDOM event that it handles.

Visualising the runloop for yourself

This repo also contains the noisy runloop kit which is trivial demo app anda copy of Ember that I have patched to be very noisy about what its runloopdoes. You can add features to the demo app and see how the actions the runloop takes inresponse in the console. You can also use the included version of Ember in your ownproject to visualise what is happening there. Obviously you should only includethis in development because it will slow the runloop down a lot.

Enough with the mousemove already!

When you start getting the runloop to log its work you will quickly getoverwhelmed by its running in response to mouse events that happen veryfrequently on desktop browsers e.g. mousemove. Below is an initializer forEmber that will stop it listening to certain events. You probably want to addthis to whatever Ember app you are trying to visualise the runloop for unlessyou are actually using mousemove, mouseenter, mouseleave in your app.

/**
 * Tell Ember to stop listening for certain events. These events are very
 * frequent so they make it harder to visualise what the runloop is doing. Feel
 * free to adjust this list by adding/removing events. The full list of events
 * that Ember listens for by default is at
 * http://emberjs.com/api/classes/Ember.View.html#toc_event-names
 *
 */

Ember.Application.initializer({
  name: 'Stop listening for overly noisy mouse events',

  initialize: function(container, application) {
    var events = container.lookup('event_dispatcher:main').events;
    delete events.mousemove;
    delete events.mouseenter;
    delete events.mouseleave;
  }
});

What are autoruns?

Calls to any of

  • run.schedule
  • run.scheduleOnce
  • run.once

have the property that they will approximate a runloop for you if one does notalready exist. These automatically (implicitly) created runloops are calledautoruns.

Lets consider an example of a click handler:

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

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

When you call schedule Ember notices that there is not a currently openrunloop so it opens one and schedules it to close on the next turn of the JSevent loop.

Here is some pseudocode to describe what happens:

$('a').click(function(){
  // 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', this, function() {
    // 2. schedule notices that there is no currently available runloop 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(function() {
          Ember.run.end()
      }, 0);
    }

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

  });

  // 4. scheduleOnce sees the autorun created by schedule above as an available
  //    runloop and adds its item to the given queue.
  Ember.run.scheduleOnce('afterRender', this, function() {
    // Yet more things
  });

});

Although autoruns are convenient you should not rely on them because:

  1. The current JS frame is allowed to end before the run loop is flushed, whichsometimes means the browser will take the opportunity to do other things, likegarbage collection.
  2. Only calls to run.schedule, run.scheduleOnce and run.once are wrappedin autoruns. All other code in your callback will happen outside Ember. Thiscan lead to unexpected and confusing behavior.

How is runloop behaviour different when testing?

We know that

  • run.schedule
  • run.scheduleOnce
  • run.once

create a new runloop if one does not exist and that these automatically(implicitly) created runloops are called autoruns.

If Ember.testing is set then this "automatic runloop approximation creation"behaviour is disabled. In fact when Ember.testing is set these three functionswill throw an error if you run them at a time when there is not an existingrunloop available.

The reasons for this are:

  1. Autoruns are Embers way of not punishing you in production if you forget toopen a runloop before you schedule callbacks on it. While this is useful inproduction, these are still issues you should fix and are revealed as such intesting mode to help you find and fix them.
  2. Some of Ember's test helpers are promises that wait for the run loop to emptybefore resolving. If your application has code that runs outside a runloop,these will resolve too early and gives erroneous test failures which can bevery difficult to find. Disabling autoruns help you identify these scenariosand helps both your testing and your application!

How do I use the runloop?

The Ember runloop API docs arethe canonical resource on what each function does. This section will provide ahigh-level overview of how the API works to make it easier to categorise it inyour head and put it to use.

In the API we have:

  • 1 way of running a given callback in a new runloop
    • Ember.run
  • 1 way of adding a callback to the currently open runloop
    • Ember.run.schedule
  • 2 ways to add a callback to the current runloop and ensure that it is only added once.
    • Ember.run.scheduleOnce
    • Ember.run.once
  • 2 ways to add a callback to some future runloop
    • Ember.run.later
    • Ember.run.next
  • 2 ways of doing rate control on a callback. These control how often a callback is called (it will get its own runloop each time)
    • Ember.run.debounce
    • Ember.run.throttle
  • 1 way of cancelling work scheduled for a future runloop or rate control
    • Ember.run.cancel
  • 2 functions provide a low-level alternative to Ember.run
    • Ember.run.begin
    • Ember.run.end
  • 1 convenience function for forcing bindings to settle
    • Ember.run.sync
Function Which runloop? Which queue? Creates new runloop? Notices Ember.testing? Runs callback in current JS event loop turn?
Ember.run always-new actions Always No Yes
Ember.run.debounce always-new actions Always No No
Ember.run.throttle always-new actions Always No No
Ember.run.join current actions If required No Yes
Ember.run.bind current actions If required No No
Ember.run.schedule current chosen by param If required Yes Yes
Ember.run.scheduleOnce current chosen by param If required Yes Yes
Ember.run.once current actions If required Yes No
Ember.run.later future actions If required Yes No
Ember.run.next future actions If required Yes No
Ember.run.begin NA NA Never No NA
Ember.run.end NA NA Never No NA
Ember.run.cancel NA NA NA NA NA
Ember.run.sync NA NA NA NA NA

Legend:

  • future = some runloop in the future
  • The default queue in Ember is actions
  • NA = not applicable

A note about future work

There are two functions in the runloop API that let us schedule "future work":

  1. Ember.run.later
  2. Ember.run.next

Each of these API functions is a way of expressing when you would like work(a callback function) to happen. The guarantee provided by the runloop is thatit will also manage the other work that results from running that function. Itdoes not guarantee anything else!

The key points:

  • Ember keeps an internal queue of "future work" in the form of an array oftimestamp and function pairs e.g. [(timestamp, fn), (timestamp, fn) ... ]
  • It uses this queue to manage work you have asked it to do on some runloop that is not the current one.
  • Each of the API functions above is a different way of adding a (timestamp, callback) pair to this array.
  • Ember does not know exactly when it will get a chance to execute this futurework (Javascript might be busy doing something else).
  • Each time it checks the timers queue it executes all the functions whose timestampsare in the past so the future work API functions are creative in theircreation of timestamps to achieve what they want.
  • When Ember does find some pairs on the future work queue that should beexecuted it creates a new runloop (using Ember.run) and schedules eachfunction onto the actions queue.

Consequences:

  • When you give a function to one of the future work API functions you cannotknow which runloop it will run in!
    • It may share a runloop with other future work functions.
    • It will only share with other functions from the future work queue
      • it will not share a runloop with other Ember code or anything youexplicitly pass to Ember.run yourself.
  • You can only directly schedule future work onto the actions queue. If you need to runsomething on a different queue of that future runloop you will need toschedule it from that actions queue callback.
  • Future work APIs let you specify some future runloop but not exactly whichone.

A note about rate control

Ember provides two flavors of rate control.

  • Ember.run.debounce
    • Ignore a callback if the previous call to it was within a given time period
  • Ember.run.throttle
    • Used to guarantee a minimum time between calls to a particular callback

These functions are useful because they allow us to control when the givencallback is not run. When it is actually run, these functions use Ember.runso these functions can be thought of as "Ember.run with some extra controlsabout when the function should be run"

Summary

It can take a while to get our heads around the subtleties of the runloop. Inexchange we get the performance and scaling benefits that the runloop provides.I hope that you now feel more equipped to use the runloop skillfully.

Happy hacking.

Appendices

Sources

The primary documentation for the Ember runloop is Official Ember Run-loopguide and the EmberAPI docs

These are other sources I studied in compiling this guide:

Other resources on the Runloop

 相关资料
  • Runloop 是和线程紧密相关的一个基础组件,是很多线程有关功能的幕后功臣。尽管在平常使用中几乎不太会直接用到,理解 Runloop 有利于我们更加深入地理解 iOS 的多线程模型。 Runloop 基本概念 Runloop 是什么?Runloop 还是比较顾名思义的一个东西,说白了就是一种循环,只不过它这种循环比较高级。一般的 while 循环会导致 CPU 进入忙等待状态,而 Runloop

  • Ember检查器是一个浏览器插件,用于调试Ember应用程序。 灰烬检查员包括以下主题 - S.No. 灰烬检查员方式和描述 1 安装Inspector 您可以安装Ember检查器来调试您的应用程序。 2 Object Inspector Ember检查器允许与Ember对象进行交互。 3 The View Tree 视图树提供应用程序的当前状态。 4 检查路由,数据选项卡和库信息 您可以看到检查

  • 英文原文: http://emberjs.com/guides/getting-ember/index/ Ember构建 Ember的发布管理团队针对Ember和Ember Data维护了不同的发布方法。 频道 最新的Ember和Ember Data的 Release,Beta 和 Canary 构建可以在这里找到。每一个频道都提供了一个开发版、最小化版和生产版。更多关于不同频道的信息可以查看博客

  • ember-emojione ember-emojione is your emoji solution for Ember, based on the EmojiOne project. EmojiOne version 2 is used, which is free to use for everyone (CC BY-SA 4.0), you're only required to giv

  • Ember 3D Ember 3D is an Ember addon for using Three.js - an easy to use, lightweight, javascript 3D library. It is designed to: Prescribe a solid file structure to Three.js code using ES6 modules. Ena

  • Ember Table An addon to support large data set and a number of features around table. Ember Table canhandle over 100,000 rows without any rendering or performance issues. Ember Table 3.x supports: Emb