Event Loop

in the browser Javascript

and the Specification

PART 1

The Good, the Bad

JavaScript has a concurrency model based on an event loop*

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

Javascript

Implementation of ECMAScript*

ECMAScript specification is about:

* Syntactic and Lexical Grammars;

* Data Types and Values;

* Execution Contexts;

* Objects, Objects Behaviours;

* Functions and Classes;

* And etc.

has not any mention about events, timers, callbacks and etc.

ECMAScript specification

Especially it's not about Event Loop

Event Loop 

is formally specified in HTML specification*

Event Loop. Processing model

 WHATWG. HTML Living standard

PART 2

Event Loop


while (eventLoop.waitForTask()) {  
  eventLoop.processNextTask()
}

It is infinitely looping and looking for new tasks to execute.

To coordinate activities: events, user interaction, scripts, rendering, networking, and so forth, user agents

what it looks like

Event Loop 

Event Loop. Task queues

An event loop has one or more task queues.

Task queue is an ordered list of tasks


while (eventLoop.waitForTask()) {  
  const taskQueue = eventLoop.selectTaskQueue()
  if (taskQueue.hasNextTask()) {
    taskQueue.processNextTask()
  }
}

Task queue

  • Events
  • Parsing
  • Callbacks
  • Using a resource
  • Reacting to DOM manipulation

It is an ordered list of tasks, which are algorithms that are responsible for such work as:

  • The DOM manipulation
  • The user interaction
  • The networking
  • The history traversal

Generic task sources*

Event Loop. Microtask queue


while (eventLoop.waitForTask()) {  
  const taskQueue = eventLoop.selectTaskQueue()
  if (taskQueue.hasNextTask()) {
    taskQueue.processNextTask()
  }

  const microtaskQueue = eventLoop.microTaskQueue
  while (microtaskQueue.hasNextMicrotask()) {
    microtaskQueue.processNextMicrotask()
  }
}

The event loop also has a single queue called the microtask queue.

The microtask queue is completely emptied in every tick after the current task finished executing.

There are two kinds of microtasks

  • solitary callback microtasks

  • compound microtasks

Microtasks

Each event loop also has a performing a microtask checkpoint flag

JavaScript job is microtask? *


while (eventLoop.waitForTask()) {  
  const taskQueue = eventLoop.selectTaskQueue()
  if (taskQueue.hasNextTask()) {
    taskQueue.processNextTask()
  }

  const microtaskQueue = eventLoop.microTaskQueue
  while (microtaskQueue.hasNextMicrotask()) {
    microtaskQueue.processNextMicrotask()
  }

  if (eventLoop.shouldRender()) {
    eventLoop.render()
  }
}

Event Loop. Render​

runTheResizeSteps()
runTheScrollSteps()

evaluateMediaQueriesAndReportChanges()

runCSSAnimationsAndSendEvents()
runTheFullscreenRenderingSteps()
runTheAnimationFrameCallbacks()
runTheUpdateIntersectionObservationsSteps()

intersectionObserverSteps()

paint()

Event Loop. Render​()*

Event Loop

2. Event Loop for Browsing Context

Each worker has one event loop. Workers have restrictions *

A browsing context event loop always has at least one browsing context

There are two kinds of event loops

1. Event Loop for Worker

Browsing

Contexts

postMessage

postMessage

Web Worker

Task Queues

Task Queues

Web Worker

Task Queues

Event Loops

Event Loop. Practice

PART 3

Example of code *


console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');

What this code will output to the console?

Event Loop. Example of code

Loupe

Little visualisation to help you understand how JavaScript's call stack/event loop/callback queue interact with each other.

Built by Philip Roberts. Code is on github.

PART 4

Crowd of browsers

Let's make a test*


<div class="outer">
  <div class="inner" />
</div>

Test preparation. HTML part

Result in the browser 

We have the following HTML code

var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');

new MutationObserver(function() {
  console.log('mutate');
}).observe(outer, {
  attributes: true
});

function onClick() {
  console.log('click');

  setTimeout(function() {
    console.log('timeout');
  }, 0);

  Promise.resolve().then(function() {
    console.log('promise');
  });

  outer.setAttribute('data-random', Math.random());
}

inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);

Test preparation. Javascript part

Click the inner square

Result according Specification

click
promise
mutate
click
promise
mutate
timeout
timeout

Result of testing

click
mutate
click
mutate
promise
promise
timeout
timeout

click
promise
mutate
click
promise
mutate
timeout
timeout

click
promise
mutate
click
promise
mutate
timeout
timeout

click
click
promise
promise
mutate
timeout
timeout

Google Chrome

Mozilla Firefox

Safari

IE Edge

Result of testing *

click
mutate
click
mutate
promise
promise
timeout
timeout

click
promise
mutate
click
promise
mutate
timeout
timeout

click
promise
mutate
click
promise
mutate
timeout
timeout

click
click
promise
promise
mutate
timeout
timeout

* After article test (which posted 17 August 2015) only Safari browser behavior was successfully changed.

Mozilla Firefox was changed also, but it not correct according specification.

Google Chrome

Mozilla Firefox

Safari

IE Edge

PART 5

Long Running code


const times = 1000
const processor = new Processor();

for (let i = 1; i < times; i++) {
  // calculation time, for example 10ms
  processor.calculate(i); 
}

console.log(
  processor.result()
)

Long running code

It will lock up the Browser for 10 seconds

There's an example of long running code*

There're solutions

Make loop via setTimeout *

Loop as little as possible

const calculate = (processor, times, callback) => {
  let i = 1;
  
  const tick = () => {
    if (i < times) {
    	processor.calculate(i);
      i++;
      setTimeout(tick);
    } else {
      callback();
    }
  }
  
  setTimeout(tick);
}

const processor = new Processor();

calculate(processor, 1000, () => {
  console.log(processor.result());
})

Extract script. Use web worker API functions *

Use Web Workers

const worker = new Worker('/path/to/script.js');

worker.postMessage('start')
worker.onmessage = (e) => {
  console.log(e.data)
}
// ..

self.addEventListener('message', (e) => { 
  if (e.data === 'start') {
    for (let i = 1; i < times; i++) {
      processor.calculate(i);
    }

    postMessage(
      processor.result()
    )
  }
}, false);

Create Worker instance. Post "start" message

Resources

Used resources

For further reading

Any questions?

Event Loop in the browser Javascript

By Ufocoder

Event Loop in the browser Javascript

  • 3,417