Before we talk more about Event-loop, let’s clarify a few basic concepts: JavaScript is single-threaded, meaning you can only do one thing at a time, but there are asynchronous tasks in the browser, and you can’t wait until the asynchronous task (in case it’s asking for a very large file and taking a long time) finishes in the normal order to do the next thing. This is clearly anti-human. The fact that the web pages we open on a regular basis don’t have such an anti-human experience shows how JavaScript works.

Execution stack and task queue

When the program runs down and encounters an asynchronous task, the main thread ignores the asynchronous task, suspends the waiting task and runs the next task first. Wait until the asynchronous task returns the result, then go back and continue the pending task. Thus, all tasks can be divided into two types, synchronous and asynchronous. A synchronization task refers to a task that is queued to be executed on the main thread. The next task can be executed only after the first task is completed. Asynchronous tasks are tasks that do not enter the main thread but enter the task queue. The task queue notifies the main thread that an asynchronous task is ready to execute.

In particular, asynchronous execution works as follows:

  • All synchronization tasks are executed on the main thread, forming an execution Context stack.
  • In addition to the main thread, there is a “task queue”. Whenever an asynchronous task has a result, an event is placed in the “task queue”.
  • Once all synchronization tasks in the execution stack are completed, the system reads the task queue to see what events are in it. Those corresponding asynchronous tasks then end the wait state, enter the execution stack, and start executing.
  • The main thread repeats step 3 above.

Marcotask and Mircotask

This is just the basics of Event loop. Now we introduce two new concepts, Marcotask and Mircotask. In the Event queue, different asynchronous tasks are stored in different queues, and they are not simply fifO.

As shown in the figure above, in a single iteration, the Event Loop first checks the MacroTask queue and, if there is a MacroTask waiting to execute, executes it. When the task completes (or the MacroTask queue is empty), the Event Loop continues to execute the MicroTask queue. If the MicroTask queue has a task waiting to execute, the Event Loop keeps fetching the task until the microTask is empty. Here we note the difference between handling microtasks and MacroTasks:A maximum of one MacroTask can be processed at a time in a single loop (the others still reside in the queue), but all microtasks can be processed.

When the MicroTask queue is empty, the Event Loop checks if a UI rerender is required and if so, rerenders the UI. This completes the loop and continues to examine the MacroTask queue from scratch.

  • Macrotasks: setTimeout setInterval setImmediate I/O UI rendering
  • microtasks: Promise process.nextTick Object.observe MutationObserver

To help us understand this, take an example:

(function () { setTimeout(function() {console.log(4)}, 0); new Promise(function executor(resolve) { console.log(1); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(2); }).then(function() { console.log(5); }); console.log(3); }) ()Copy the code

Analysis process:

  1. The task is currently running, executing code. First the setTimeout callback is added to the Tasks queue;
  2. Instantiate promise, print 1; Promise resolved; Output 2;
  3. The promise.then callback is added to the MicroTasks queue;
  4. The output 3;
  5. At the end of the current task, execute microtasks and output 5.
  6. Execute the next task, printing 4.

Web Worker

HTML5 proposes the Web Worker standard, which allows JavaScript scripts to create multiple threads, but the child threads are completely controlled by the main thread and cannot operate DOM, which does not change the fact that JavaScript is single threaded.

Dedicated Web Worker examples:

// html
    <form>
      <div>
        <label for="number1">Multiply number 1: </label>    
        <input type="text" id="number1" value="0">
      </div>
      <div>
        <label for="number2">Multiply number 2: </label>   
        <input type="text" id="number2" value="0">
      </div>
    </form>

    <p class="result">Result: 0</p>
<script>
var first = document.querySelector('#number1');
var second = document.querySelector('#number2');

var result = document.querySelector('.result');

if (window.Worker) { // Check if Browser supports the Worker api.
	var myWorker = new Worker("worker.js");

	first.onchange = function() {
	  myWorker.postMessage([first.value,second.value]); // Sending message as an array to the worker
	  console.log('Message posted to worker');
	};

	second.onchange = function() {
	  myWorker.postMessage([first.value,second.value]);
	  console.log('Message posted to worker');
	};

	myWorker.onmessage = function(e) {
		result.textContent = e.data;
		console.log('Message received from worker');
	};
}
</script>

//work.js

onmessage = function(e) {
  console.log('Message received from main script');
  var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
  console.log('Posting message back to main script');
  postMessage(workerResult);
}
Copy the code

As you can see from the code above, both pass data via postMessage.

Refer to the article

More on the Event Loop

Look at Event loops, Tasks, and Microtasks in JavaScript from promises

HTML series: MacroTask and MicroTask