Let’s look at some code:

console.log(1);

setTimeout(function () {
	console.log(2);

	new Promise(function (resolve, reject) {
		console.log(3);
		resolve();
		console.log(4);
	}).then(function () {
		console.log(5);
	});
});

function fn() {
	console.log(6);
	setTimeout(function () {
		console.log(7);
	}, 50);
}

new Promise(function (resolve, reject) {
	console.log(8);
	resolve();
	console.log(9);
}).then(function () {
	console.log(10);
});

fn();

console.log(11);

// The following code needs to be executed in the Node environment
process.nextTick(function () {
	console.log(12);
});

setImmediate(function () {
	console.log(13);
});
Copy the code

Think about it, can you give me the exact order of output?

Now we will understand the knowledge points related to Event Loop one by one, and finally analyze the final output sequence of this code step by step.

JavaScript is single-threaded

First let’s look at the concept and relationship between process and thread:

  • Process: A running program is a process, such as the browser you are running, which has a process.
  • Thread: A segment of code that runs independently in a program. A process consists of one or more threads, which are responsible for executing code.

We all know that JavaScript is single threaded, so since there are single threads, there are multiple threads. Let’s first look at the difference between single threads and multiple threads:

  • Single thread: execute from beginning to end, line by line. If one line of code fails, the rest of the code will not execute. It is also easy to block code.
  • Multithreading: different code running environment, each thread independent, do not affect each other, to avoid blocking.

So why is JavaScript single threaded?

The single thread of JavaScript, relative to its purpose. As a browser scripting language, JavaScript’s primary purpose is to interact with users and manipulate the DOM. This means that it has to be single-threaded, which can cause complex synchronization problems. For example, if there are two threads of JavaScript at the same time, one thread adds content to a DOM node, and the other thread removes it, which thread should the browser use?

So, to avoid complexity, JavaScript has been single-threaded since its inception, and this has been a core feature of the language and will not change.

In order to make use of the computing power of multi-core CPU, 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. So, this new standard doesn’t change the single-threaded nature of JavaScript.

Execution stack, task queue

In the figure above, when the main thread is running, the heap and stack are created, and the code in the stack calls various external apis, which add various events (DOM events, Ajax, setTimeout…) to the “task queue”. . As soon as the stack completes, the main thread reads the task queue and executes the corresponding callback function for those events.

Heap:

Objects are allocated in a heap, which represents a largely unstructured area of memory.

Execute stack:

Run the synchronous code. The code in the stack (synchronous tasks) is always executed before the “task queue” (asynchronous tasks) is read.

Callback Queue:

A “task queue” is a queue of events (also known as a message queue). When an IO device completes a task, it adds an event to the “task queue”, indicating that the related asynchronous task can be placed on the “execution stack”. The main thread reads the “task queue”, which is to read what events are in it.

In addition to the IO device events, the “task queue” includes some user-generated events (such as mouse clicks, page scrolling, and so on). As long as the callback function is specified, these events are queued up for the main thread to read.

Callbacks are code that is suspended by the main thread. Asynchronous tasks must specify a callback function, which is executed when the main thread starts executing an asynchronous task.

A “task queue” is a first-in, first-out data structure in which the first events are read by the main thread first. The reading of the main thread is basically automatic. As soon as the stack is emptied, the first event in the “task queue” is automatically sent to the main thread. However, due to the “timer” function mentioned later, the main thread first checks the execution time, and certain events cannot be returned to the main thread until the specified time.

Synchronous task, asynchronous task, macro task, micro task

Single threading means that all tasks need to be queued until the first one is finished before the next one can be executed. If the first task takes a long time, the second task has to wait forever.

If the queue is due to a large amount of computation and the CPU is too busy, it is fine, but many times the CPU is idle because the IO devices (input and output devices) are slow (such as Ajax operations reading data from the network) and have to wait for the results to come out before executing.

The designers of the JavaScript language realized that the main thread could simply ignore the IO device, suspend the pending task and run the next one first. Wait until the IO device returns the result, then go back and continue the pending task.

Thus, all JavaScript tasks can be categorized broadly 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 like this (as does synchronous execution, since it can be considered asynchronous execution without asynchronous tasks) :

(1) All synchronization tasks are executed on the main thread, forming an "execution context stack"; (2) 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"; (3) Once all synchronization tasks in the "execution stack" are completed, the system will take out the callback function corresponding to the events in the "task queue" and enter the "execution stack" to start execution; (4) The main thread repeats step 3 above.Copy the code

In addition to the broad definition, we can further define tasks into macro tasks and micro tasks:

  • Macro task: includes overall code script, setTimeout, setInterval, Ajax, DOM manipulation
  • Micro-task: Promise

Specifically, the operation mechanism of macro task and micro task execution is as follows:

(1) First, complete the execution of all synchronization codes (macro tasks) at the beginning of the "execution stack"; (2) Check whether there are microtasks, if so, perform all the microtasks; (3) Retrieve the callback function (macro task) corresponding to the event in the "task queue" into the "execution stack" and complete the execution; (4) Check whether there are microtasks, if so, execute all the microtasks; (5) The main thread repeats steps (3) and (4) above.Copy the code

In both cases, the main thread reads events from the “task queue” in a continuous Loop, so the whole operation mechanism is also called an Event Loop.

SetTimeout (), setInterval ()

SetTimeout () and setInterval() work exactly the same internally, except that the former specifies code to execute once, while the latter executes repeatedly.

SetTimeout () and setInterval() generate asynchronous and macro tasks.

SetTimeout () takes two arguments, the first being the callback function and the second the number of milliseconds to delay execution. SetInterval () takes two arguments, the first a callback function and the second the number of milliseconds to execute repeatedly.

If the second parameter is set to 0 or not, it does not mean to execute immediately, but rather to specify that a task should be executed at the earliest available free time on the main thread, that is, as early as possible. It adds an event to the end of the “task queue”, so it does not execute until both the synchronized task and the existing events in the “task queue” are processed.

Therefore, setTimeout() and setInterval() are not absolute. They need to be determined according to the final execution time of the current code. To put it simply, If the current code execution time (such as execution of 200ms) exceeds the delay (setTimeout(fn, 100)) or repeated execution (setInterval(fn, 100)), There is no difference between setTimeout(fn, 100) and setTimeout(fn, 0), and there is no difference between setInterval(fn, 100) and setInterval(fn, 0).

Promise

Promises are relatively special. The callback passed in new Promise() is executed immediately, but its then() method is executed after the execution stack and before the task queue, which is a microtask.

process.nextTick

Process.nexttick is a “task queue” method provided by Node.js. It produces tasks at the end of the execution stack, not macro or micro tasks, so its tasks always occur before all asynchronous tasks.

setImmediate

SetImmediate setImmediate is another “task queue” method provided by Node.js. It generates tasks that append to the end of the “task queue”. It is similar to setTimeout(fn, 0), but setTimeout takes precedence over setImmediate.

Sometimes setTimeout is executed before setImmediate, and sometimes after setImmediate. This is not a Node.js bug, because while setTimeout’s second parameter is set to 0 or none, However, setTimeout source code will specify a specific number of milliseconds (node is 1ms, browser is 4ms), and the current code execution time is affected by the execution environment, the execution time fluctuates, if the current code execution is less than the specified value, setTimeout has not reached the time to postpone execution. SetImmediate naturally executes first, and setTimeout precedes setImmediate if the code currently executing exceeds the specified value.

priority

From the above, we can derive a code execution priority:

Synchronizing code (macro task) > process.nextTick > Promise (microtask) > setTimeout(fn), setInterval(FN) > setImmediate (macro task) > setTimeout(fn, Time) and setInterval(fn, time), where time>0

Code parsing

Going back to the code given at the beginning, let’s parse it step by step:

Run the code in “execute stack” :

console.log(1);

// setTimeout(function () {// setTimeout(function () {
// console.log(2);
//
// new Promise(function (resolve, reject) {
// console.log(3);
// resolve();
// console.log(4);
// }).then(function () {
// console.log(5);
/ /});
// });

function fn() {
	console.log(6);
	//setTimeout(function () {//setTimeout(function () {
	//	console.log(7);
	/ /}, 50);
}

new Promise(function (resolve, reject) {
	console.log(8);
	resolve();
	console.log(9);
})
//. Then (function () {//
// console.log(10);
// });

fn();

console.log(11);

process.nextTick(function () {
	console.log(12);
});

SetImmediate (function () {// Do not mediate() {
// console.log(13);
// });
Copy the code

The output is 1, 8, 9, 6, 11, 12

Running microtasks:

new Promise(function (resolve, reject) {
	// console.log(8); / / has been executed
	// resolve(); / / has been executed
	// console.log(9); / / has been executed
})
.then(function () {
	console.log(10);
});
Copy the code

The output is: 10

Read the callback function of “task queue” into “execution stack” :

setTimeout(function () {
	console.log(2);

	new Promise(function (resolve, reject) {
		console.log(3);
		resolve();
		console.log(4);
	})
	//. Then (function () {//
	//	console.log(5);
	/ /});
});
Copy the code

The output is: 2, 3, and 4

Run the microtask again:

setTimeout(function () {
	// console.log(2); / / has been executed

	new Promise(function (resolve, reject) {
		// console.log(3); / / has been executed
		// resolve(); / / has been executed
		// console.log(4); / / has been executed
	})
	.then(function () {
		console.log(5);
	});
});
Copy the code

The output is: 5

Then read the callback function of “task queue” into “execution stack” :

setImmediate(function () {
	console.log(13);
});
Copy the code

The output is 13

Running microtasks:

There is no

Then read the callback function of “task queue” into “execution stack” :

// function fn() {// Done
	// console.log(6); / / has been executed
	setTimeout(function () {
		console.log(7);
	}, 50);
// }
Copy the code

The output is: 7

Running microtasks:

There is no

To sum up, the final output sequence is: 1 8 9 6 11 12 10 2 3 4 5 13 7

The resources

  • The Event Loop in javascript
  • More on the Event Loop
  • Concurrency model and event loop
  • This time, thoroughly understand the JavaScript execution mechanism
  • Road of Node event loop (2) — setTimeout/setImmediate/process. The difference between nextTick

Please indicate the source of reprint, thank you!