First, why should there be an event loop

The browser runs on multiple tasks at the same time, such as user interaction events (mouse, keyboard), network requests, page rendering, etc. And these tasks can’t be unordered, they have to come first, they need a set of predefined logic inside the browser to process these tasks in order, so the browser event loop is born, again, the browser event loop, not the javascript event loop, JS is just a participant in the browser event loop.

What is the cycle of events

The browser divides the task area into macro task and micro task or called external task and internal task, internal task can be understood as js internal processing task, external task can be considered as browser processing task.

External Queue/Macro Task Queue

Also known as macro task queues, external event sources in browsers include the following:

  • Dom manipulation (page rendering)
  • User interaction (mouse, keyboard)
  • Web requests (Ajax, etc.)
  • History API operations (history.back, history.go…)
  • Timer (setTimeout)

These external event sources can be numerous, and to facilitate optimization by browser vendors, the HTML standard specifies that an event loop has one or more external queues, and that each external event source has a corresponding external queue. Different time sources can have different priorities (for example, between network time and user interaction, the browser can prioritize mouse behavior to make the user feel more fluid).

Internal Queue/Microtask Queue

It can also be called microtask queue, which refers to the event queue inside javascript language. In THE HTML standard, the event source of this queue is not clearly specified, and it is generally considered as the following:

  • The success and failure of promises
  • MutationObserver
  • Object. Observe (deprecated)

Except for the first one, the other two can be considered as none. In fact, we can only use promise in JS.

Event cycle model

Here’s a screenshot of the event loop processing model:

As you can see, for each event loop, one is executed from the external task queue, all internal tasks in the internal task queue are executed immediately after the external task is executed (emptied), and then the browser performs a rendering, and then loops again.

A classic piece of code

Given the execution models of the two queues and event loops, here’s some classic code:

// What output does the following code produce?
console.log('1');
setTimeout(function() { 
  console.log('2'); 
  Promise.resolve().then(function() {
		console.log('3'); 
  });
}, 0);
Promise.resolve().then(function() { 
  console.log('4');
}).then(function() { 
  console.log('5');
});
console.log('6');
Copy the code

The answer is: 164523

The execution sequence is as follows:

  • Since the task of executing the current JS code is a macro task, the first output is “1”,
  • Continuing execution encounters setTimeou. Since setTimeout is an external event source, its internal code is pushed to the TaskQueue to wait for the next event loop to execute,
  • When a promise’s THEN or CatchD is executed, they are sequentially appended to the end of the event loop,
  • After the macro task is complete, the tasks in the microtask queue will be emptied, and then output 4 and 5
  • If so, the loop ends after the render task is executed
  • Start the next macro task, the block in the first setTimeout, print 2, and add promise.then to the end of the loop
  • Clear the microtask and print 3

Third, browser and Node.js event loop differences

Difference between contrast

Here’s a quick snapshot of the difference:

The code for this example is as follows:

setTimeout((a)= >{ 
  console.log('1'); 
  Promise.resolve().then(function() {
		console.log('2'); 
	});
});
setTimeout((a)= >{ 
  console.log('3'); 
  Promise.resolve().then(function() {
		console.log('4'); 
  });
});
Copy the code

What is the output of this code in the browser and nodeJS?

From what you’ve seen before in the browse event loop, you should easily conclude that the output in the browser is: 1234

What is the output in NodeJS? The result is 1324 output before nodeJS’s V11.x. Between the browser has a huge number of user interaction is the cause of the event, to the user experience more fluent, macro and micro tasks must be uniform processing, and because no user interaction in nodejs events, in order to guarantee execution of asynchronous events can be equal, so the purpose of the design is empty macro task queue to empty the task queue.

However, you should note that I only said 1324 was exported before THE V11.x release of Nodejs, but after a wave of devs teasing the nodejs feature in the community, nodejs officially has an emergency fix for v11. So executing the above code in v11.x or later will give the same result as in the browser.

setImmediate

Here’s a quick snapshot:

Let’s do another example:

setTimeout((a)= >{
	console.log('1');
	Promise.resolve().then((a)= > console.log('2'));
});
setTimeout((a)= >{
  console.log('3');
  Promise.resolve().then((a)= > console.log('4'));
});
setImmediate((a)= > {
	console.log('5'); 
  Promise.resolve().then((a)= > console.log('6'));
});
setImmediate((a)= > {
	console.log('7'); 
  Promise.resolve().then((a)= > console.log('8'));
});
Copy the code

The result of the above code in nodejsv13.x is 12345678. Let’s switch the sequence and insert setImmediate in the second position

setTimeout((a)= >{
	console.log('1');
	Promise.resolve().then((a)= > console.log('2'));
});
setImmediate((a)= > {
	console.log('3'); 
  Promise.resolve().then((a)= > console.log('4'));
});
setTimeout((a)= >{
  console.log('5');
  Promise.resolve().then((a)= > console.log('6'));
});
setImmediate((a)= > {
	console.log('7'); 
  Promise.resolve().then((a)= > console.log('8'));
});
Copy the code

The execution result has a certain probability of 12347856 and a certain probability of 12563478

Why do different orders get different results? This is due to the accuracy of setTImeout. Up to this level of time precision, the time of code execution may lead to different results. Here is a screenshot of the official NodeJS documentation showing the sequence of events:

The Timers stage is used to perform setTimeout events and the Check stage is used to perform setImmediate events. Nodejs officially calls the event loop process, but in fact, it is only the priority of node.js external queues in the complete event loop. SetTimeout is a callback method of the event loop that detects whether the system time is up and inserts an event into the time queue. SetImmediate, on the other hand, monitors the UI thread’s call stack and calls back the stack if it is empty.

With all that said, the comparison between setTimeout and setImmediate is a bit ambiguous

The setImmediate delay is sometimes longer than the setTimeout delay, because setImmediate monitors the stack and then pushes the setTimeout callback function before the stack is empty.

Ok, the above is all the content shared this time, there is no clear result for the comparison of setTimeout and setImmedate, those who are interested can discuss together.