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 distribute
new
The 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
Get the contents of the script, or the input file;
Wrap the above in a function;
As an event handler for the “start” or “launch” events associated with the program;
Perform other initializations;
Issue a program start event;
Events are added to the event queue;
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
.setImmediate
And 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…