If JavaScript is single-threaded, how do we create and run threads as we do in Java?

Very simply, we use events or set a piece of code to execute at a given time. This asynchrony is called an Event loop in JavaScript.

In this article, I would like to analyze two points:

  • How the Event Loop system in Javascript works;
  • Implement a custom Javascript engine to explain how the Event Loop system works and demonstrate its task queue and execution cycle.

Event Loop in JavaScript

JavaScript consists of Stack stacks, Heap stacks, and Task queues:

  • Stack: used to be an array-like structure that keeps track of functions currently being executed;
  • Heap: Used to distributenewThe object created;
  • Task Queue: It is used to process asynchronous tasks. When the Task is completed, the corresponding callback is queued.

When running the following synchronization tasks

console.log('script start');
console.log('script end');
Copy the code

JavaScript executes the code in turn, starting with the script in the following steps

  1. Get the contents of the script, or the input file;

  2. Wrap the above in a function;

  3. As an event handler for the “start” or “launch” events associated with the program;

  4. Perform other initializations;

  5. Issue a program start event;

  6. Events are added to the event queue;

  7. The Javascript engine pulls the event from the queue, executes the registered handler, and then runs! – “Asynchronous Programming in Javascript CSCI 5828: Foundations of Software Engineering Lectures 18 — 10/20/2016 “by Kenneth M. Anderson

To summarize, the Javascript engine wraps the script content around the Main function and associates it with the corresponding handler for the start or launch event of the program, and then the Main function goes into the Stack, Console. log(‘script start’) is then encountered, pushed onto the stack, output log(‘script start’), and left on the stack until all code has been run.

If there are asynchronous tasks

console.log('script start');
setTimeout(function callback() {
    console.log('setTimeout');
}, 0);
console.log('script end');
Copy the code

First step, as shown above, run console.log(‘script start’) and then encounter **WebAPIs ** (DOM, Ajax, setTimeout)

Call setTimeout(function callback() {}) to get a Timer, continue with console.log(‘end’).

If the timer is completed, the corresponding callback is added to the Task Queue.

The callback is then pushed onto the Stack when all the functions in the Stack have run (the key to the Event Loop: if the Stack is empty, the tasks in the Task Queue are read in first-in, first-out order).

So the result of the above code is as follows

console.log('script start');
setTimeout(function callback() {
	console.log('setTimeout');
}, 0);
console.log('script end');
// log script start
// log script end
// setTimeout
Copy the code

This is how the viewer performs an asynchronous task using Event Loop.

Microtask and Macrotask as well as implementing JS engines

Microtask and Macrotask are asynchronous tasks and each includes the following apis:

  • Microtask:process.nextTick.Promises.MutationObserver;
  • Macrotask:setTimeout.setInterval.setImmediateAnd so on.

The Macrotask queue is the task queue, while Microtasks are usually scheduled to be executed after the currently executing synchronization task and need to be processed in the same period as all Microtasks in the current queue, as shown below

for (macroTask of macroTaskQueue) {
    // 1. Handle macroTask
    handleMacroTask();
      
    // 2. Process all microtasks in the current microTaskQueue
    for (microTask ofmicroTaskQueue) { handleMicroTask(microTask); }}Copy the code

Execute the following code

// 1. Stack log "script start"
console.log("script start");
// 2. Run webAPi and enter the Task queue with anonymous function
setTimeout(function() { 
    console.log("setTimeout");
}, 0);
new Promise(function(resolve) {
    // 3. Run log "promise1" immediately.
    console.log("promise1");
    resolve();
}).then(function() {
    // 4. MicroTask is scheduled after the currently executing synchronization task
    console.log("promise2");
}).then(function() {
    / / 5. Same as above
    console.log("promise3");
});
// 6. log "script end"
console.log("script end");
/*
script start
promise1
script end
promise2
promise3
setTimeout
*/
Copy the code

So the output is 1 -> 3 -> 6 -> 4 -> 5 -> 2.

Next, using Javascript to emulate the JS Engine, this section gives priority to the article Microtask and Macrotask: A hands-on Approach, and then bugs the following code.

First in JSEngine internal maintenance of macro task, micro task two queues macroTaskQueue, microTaskQueue and the corresponding jsStack stack, and define the relevant operations.

class JsEngine {
      macroTaskQueue = [];
      microTaskQueue = [];
      jsStack = [];

      setMicro(task) {
        this.microTaskQueue.push(task);
      }
      setMacro(task) {
        this.macroTaskQueue.push(task);
      }
      setStack(task) {
        this.jsStack.push(task);
      }
	  setTimeout(task, milli) {
        this.macroTaskQueue.push(task); }}Copy the code

Next you define the relevant running mechanisms and initialization operations

class JsEngine {...// Corresponds to initialization in event-loop
    constructor(tasks) {
        this.jsStack = tasks;
        this.runScript(this.runScriptHandler);
    }
    runScript(task) {
    	this.macroTaskQueue.push(task);
    }
	runScriptHandler = (a)= > {
        let curTask = this.jsStack.shift();
        while (curTask) {
          	this.runTask(curTask);
          	curTask = this.jsStack.shift();
        }
    }
    runMacroTask() {
        const { microTaskQueue, macroTaskQueue } = this;
		// Define macroTaskQueue and microTaskQueue execution order according to the above rules
        macroTaskQueue.forEach(macrotask= > {
        	macrotask();
          	if (microTaskQueue.length) {
            	let curMicroTask = microTaskQueue.pop();
            	while (curMicroTask) {
              		this.runTask(microTaskQueue); curMicroTask = microTaskQueue.pop(); }}}); }/ / run the task
    runTask(task) {
    	new Function(task)(); }}Copy the code

Run the following code using the Js Engine above

const scriptTasks = [
      `console.log('start')`.`console.log("Hi, I'm running in a custom JS engine")`.`console.log('end')`
    ];
const customJsEngine = new JsEngine(scriptTasks);
customJsEngine.setTimeout((a)= > console.log("setTimeout"));
customJsEngine.setMicro(`console.log('Micro1')`);
customJsEngine.setMicro(`console.log('Micro2')`);
customJsEngine.runMacroTask();
Copy the code

End up with the result

start
Hi, I'm running in a custom JS engine
end
Micro1
setTimeout
Copy the code

conclusion

Did some research, reviewed some videos, and combed through the above questions.

reference

  • www.youtube.com/watch?v=8aG…
  • Blog. Bitsrc. IO/microtask – a…
  • Juejin. Cn/post / 684490…