introduce
This is part 17 of the advanced JavaScript tutorial, which covers async, await, and event loops
The body of the
1. async/await
1.1 async
The async keyword declares an asynchronous function:
async
是asynchronous
Asynchronous, asynchronous, asynchronoussync
是synchronous
Synchrony, for synchronization
async function foo() {}
const bar = async() = > {}class Baz {
async foo(){}}Copy the code
1.2 Differences between asynchronous functions and ordinary functions
Different return values
async function foo() {
return 'foo'
}
// The return value of the asynchronous function must be a Promise instance
const p = foo()
p.then(res= > {
console.log(res)
})
// Return a Promise instance or thenable
async function bar() {
return {
then(resolve) {
resolve('bar')}}}async function baz() {
return new Promise(resolve= > {
resolve('baz')})}Copy the code
1.3 await
Another special feature of async is that you can use the await keyword inside it, as normal functions cannot
Await can be followed by Promise instances, asynchronous functions, thenable, plain values
async function foo() {
return new Promise(resolve= > {
setTimeout(() = > {
console.log('foo execute')
resolve()
}, 2000)})}async function bar() {
// The following code is not executed until the foo code is executed
// It is like all the code is executed synchronously
await foo()
console.log('bar execute')
}
bar()
Copy the code
If the state of the Promise instance after await is Rejected, it will be the rejected value of the await asynchronous function
async function foo() {
return new Promise((resolve, reject) = > {
setTimeout(() = > {
console.log('foo execute')
reject('error')},2000)})}async function bar() {
await foo()
// This line will not be executed because the above code is rejected
console.log('bar execute')
}
bar().catch(err= > {
The // reject value will be passed here
console.log('err', err)
})
Copy the code
2. The browser event loop
2.1 Processes and Threads
Processes and threads are two concepts in the operating system:
Process (process)
: computerA program that is already runningIs a way for an operating system to manage programsThread (thread)
: The operating system can run properlyThe smallest unit of operational schedulingUsually it isIncluded in the process
Colloquially:
- Process: A process (or processes) is created when an application is started
- Threads: In each process, at least one thread is created to execute code in the program. This thread is called the main thread
- So a process is a container for threads
2.2 Operating System Working Mode
How does an operating system allow multiple processes to work at the same time?
- This is because the CPU is so fast that it can quickly switch between multiple processes
- When the thread in our process gets the time slice, we can quickly execute our code
- The switch is invisible to the user
If you are using a single-core CPU, the CPU will switch between multiple processes during normal operation
2.3 JS Threads in the Browser
JS is often said to be single-threaded, but the JS thread should have its own container: browser process or Node process
Does the browser only have one process?
- In modern browsers (especially Chrome), each page has its own rendering process, and there are many threads in each process, such as a thread that executes JS code.
JS code is executed in a thread:
- That is, JS can only do one thing at a time
- If this is time consuming, thread blocking will occur
So the real time-consuming operations are not performed by the JS thread:
- Each process in the browser is multi-threaded, so other threads can do this time-consuming operation
- Such as network requests, timers, etc., we just need to perform callbacks
2.4 Browser event Loops
What if, in the course of executing JS code, an asynchronous operation occurs?
- Let’s say we call
setTimeout
This function - This function is placed on the call stack and execution ends immediately without blocking subsequent code
- The timing operation is handed over to another thread to execute and called when the timing is complete
setTimeout
The callback function passed in
So we can see how some asynchronous operations are handled by the browser:
- In order not to block the thread, some functions/code that might have a callback will be executed immediately
- Operations such as timing are left to other threads
- The browser maintains an internal queue of functions that are operating asynchronously in the process. This queue is called the event queue
- When it finally finishes, the appropriate callback will be invoked, for example
setTimeout
The timer parameter passed in - And that process is the cycle of events.
2.5 Macro and micro tasks
In 2.4, we learned that the browser maintains an event queue, which is actually divided into macro task queues (MarcotaskQueue) and micro task queues (MircotaskQueue).
To join a macro task queue:
- Timers (setTimeout, setInterval)
- ajax
- DOM (click events, etc.)
- UI rendering operations
- .
Actions to join microtask queues:
queueMircotask
Promise.then
MutationObserver
- .
Which task will be executed first, macro or micro?
- Specification: Ensure that the microtask queue has been emptied before any macro task is executed
- That is, before any macro task is executed, all the microtasks in the microtask queue must be executed first
So the event loop looks like this:
- Execute main script
- Adds tasks from the microtask queue to the main script
- Add the queue in the macro task to the main script (at the time of executing each macro task, verify that the microtask queue is empty, if not, execute all the microtasks before executing the macro task)
3. Browser event loop
Next, we use interview questions to consolidate macro and micro tasks
3.1 Interview question 1
setTimeout(() = > {
console.log('setTimeout1')
new Promise(resolve= > {
resolve()
}).then(() = > {
new Promise(resolve= > {
resolve()
}).then(() = > {
console.log('then4')})console.log('then2')})})new Promise(resolve= > {
console.log('promise1')
resolve()
}).then(() = > {
console.log('then1')})setTimeout(() = > {
console.log('setTimeout2')})console.log(2)
queueMicrotask(() = > {
console.log('queueMicrotask1')})new Promise(resolve= > {
resolve()
}).then(() = > {
console.log('then3')})/ / the result
// promise1 2 then1 queueMircotask1 then3 setTimeout1 then2 then4 setTiemout2
Copy the code
This problem is very simple, let’s look at the analysis:
- Start of sync code: Execute new Promise on line 16, execute executor, print
promise1
- Execute line 27 print, print
2
- Execute new Promise on line 33, execute executor, no print code, synchronization code ends
- The microtask queue starts emptying: Because line 19 promise. then is a microtask, executed before setTimeout on line 1, printing
then1
- Because queueMicrotask on line 29 is a microtask, print it
queueMicrotask1
- Because line 36 promise. then is a microtask, executed before setTimeout on line 1, printing
then3
.The microtask queue is cleared - Start each macro task: Perform the timer callback for setTimeout on the first line
- Start of sync code: Perform line 2 print, print
setTimeout1
- Execute new Promise on line 4, execute executor, no print code, sync code ends
- Start emptying the microtask queue: execute promise.then on line 6
- ** Sync code begins: ** New Promsie on line 7 executes executor without printing code
- Execute line 12 print, print
then2
.End of synchronization code - Start emptying the microtask queue: Execute promise. then on line 9, print
then4
.The microtask queue ends - The macro task starts: Perform the macro task in line 23 to print
setTimeout2
- The macro task queue is cleared and the code is executed
3.2 Interview question 2
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')}async function async2() {
console.log('async2')}console.log('script start')
setTimeout(() = > {
console.log('setTimeout')},0)
async1()
new Promise(resolve= > {
console.log('promise1')
resolve()
}).then(() = > {
console.log('promise2')})/ / the result
// script start async1 start async2 promise1 async1 end promise2 setTimeout
Copy the code
Before we look at the analysis, let’s look at a knowledge point:
async function foo() {
console.log('foo')}async function bar() {
console.log('bar start')
await foo()
console.log('bar end')
}
bar()
console.log('main script end')
// Bar end is a microtask
// Because we said in async/await that an asynchronous task must return a Promise, and
// await foo() code that wants to execute after executing foo will include the Promise returned in foo
// then
/ / equivalent to
async function foo() {
console.log('foo')
return new Promise(resolve= > {
resolve()
}).then(() = > {
console.log('bar end')})}Copy the code
The interview questions are easy to answer.
- Start of sync code: Prints 11 lines
script start
- Execute async1 to print line 2
async1 start
- Execute async2 and print line 8
async2
- The executor that executes the New Promise on line 19, prints
promise1
.End of synchronization code - Start clearing microtasks: Execute line 4 to print
async1 end
- Go to line 23, print
promise2
.The microtask queue is cleared - Start emptying macro tasks: Executes the callback passed in by setTimeout on line 13, printing
setTimeout
.The macro task is cleared - Code execution complete
3.3 Interview question 3
Promise.resolve()
.then(() = > {
console.log(0)
return Promise.resolve(4)
})
.then(res= > {
console.log(res)
})
Promise.resolve()
.then(() = > {
console.log(1)
})
.then(() = > {
console.log(2)
})
.then(() = > {
console.log(3)
})
.then(() = > {
console.log(5)
})
.then(() = > {
console.log(6)})Copy the code
This is an odd order of execution, but let’s change the code:
Promise.resolve()
.then(() = > {
console.log(0)
// There is a change here
return 4
// resolve(4)
})
.then(res= > {
console.log(res)
})
// The rest of the code is left untouched
// It is easy to infer that the result is:
// 0 1 2 3 5 6
Copy the code
Next, let’s change it again:
Promise.resolve()
.then(() = > {
console.log(0)
// There is a change here
return {
then(resolve) {
resolve(4)
}
}
})
.then(res= > {
console.log(res)
})
// The rest of the code is left untouched
// The result is changed again
// 0 1 2 3 5 6
Copy the code
Why?? This is because the execution of the THEN function itself is placed in the next microtask (if the then returns anything other than a normal value, such as Thenable or Promise, it is deferred until the next microtask). Therefore, it is not difficult to understand that the order of the 4 is delayed by another one because then itself delays the microtask by one
Resolve (4) delays two microtasks in total because promise.resolve (4) delays another microtask.
So you end up doing 0, 1, 2, 3, 4, 5, 6
Promises/A+ : Promises/A+ Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+
Why is this operation delayed? If it returns a normal value, then there is no complication, but if it returns thenable or Promise, then there might be a lot of computation in this function, and performing it immediately might block the execution of the following microtask, thus postponing it to the next one.
4. Node event loop
The EventLoop in the browser is implemented according to the specification defined by HTML5, and different browsers may have different implementations, while the Node implementation loop is implemented according to the libuv library.
Here’s a look at the architecture of Node.js:
- We found that Libuv mainly maintains EventLoop and Worker Threds.
- EventLoop is responsible for invoking other operations of the system: file IO, Network, child-processes, and so on
Libuv is a cross-platform library that focuses on asynchronous IO. Originally developed for Node.js, libuv has also been used by Luvit, Julia, Pyuv, etc
4.1 Node Event cycle stages
The event loop is like a bridge between the application’s JavaScript and the system call:
- Whether it is our file IO, database IO, network IO, timer, child process, after completing the corresponding operation, will put the corresponding result and callback function into the event loop (task queue)
- The event loop will continuously pull the corresponding event (callback function) ** from the ** task queue to execute
A complete event cycle Tick is divided into several phases:
- Timers: This phase has been executed
setTimeout()
和setIterval()
The scheduling callback function of - Pending Callback: Performs a Callback for certain system operations (such as TCP error types), such as ECONNREFUSED when TCP connections are received
- Idle, prepare: used only in the system
- Poll: Retrieves new I/O events and performs I/ O-related callbacks
- Check:
setImmediate()
This is where the callback function is executed - The closed callback function: some closed callback functions, such as
socket.on('close', ...)
4.2 Node macro and micro tasks
In the Tick of a Node event loop, we find that the Node event loop is more complex, which is also divided into macro tasks and micro tasks in Node:
- MacroTasks:
setTimeout
,setInterval
,IO events
,setImmediate
,The close event
- Microtasks:
Promise back then
,process.nextTick
,queueMicrotask
But event loops in Node aren’t just for task queues and macro task queues:
- Microtask queue:
next tick queue
: the process nextTickother queue
: promise. then callback, queueMicrotask
- Macro task queue:
timer queue
: setTimeout and setIntervalpoll queue
: IO eventscheck queue
: setImmediateclose queue
: close events
Therefore, each event loop tick is executed in the following order:
- next tick mircotask queue
- other microtask queue
- timer queue
- poll queue
- check queue
- close queue
5. Node event loop
5.1 Interview question 1
async function async1() {
console.log('async 1 start')
await async2()
console.log('async 1 end')}async function async2() {
console.log('async2')}console.log('script start')
setTimeout(() = > {
console.log('setTimeout0')},0)
setTimeout(() = > {
console.log('setTimeout2')},300)
setImmediate(() = > console.log('setImmediate'))
process.nextTick(() = > console.log('nextTick1'))
async1()
process.nextTick(() = > console.log('nextTick2'))
new Promise(resolve= > {
console.log('promise1')
resolve()
console.log('promise2')
}).then(() = > {
console.log('promise3')})console.log('script end')
// Order of execution
// script start
// async 1 start
// async2
// promise1
// promise2
// script end
// nextTick1
// nextTick2
// async 1 end
// promise3
// setTimeout0
// setImmediate
// setTimeout2
Copy the code
parsing
According to the analysis, it is not difficult:
- First, execute the main script: Execute line 11, print
script start
- Line 25 async 1, line 2, print
async 1 start
- Await line 3 async2, line 8 print
async 2
The code in line 4 is a callback to promise.then, so it will not be executed now - Print line 30
promise1
To print line 32promise2
- Print on line 37
script end
.The main script to complete - Start emptying the microtask queue: Execute line 23 in the order described in 4.2 to print
nextTick1
- Go to line 27, print
nextTick2
- Go to line 4, print
async1 end
- Go to line 34, print
promise3
.Microtask complete - The macro task queue starts to clear: Run the timer in line 17 to print
setTimeout0
- Go to line 21, print
setImmediate
.Macro task completed - Wait for the timer on line 17 to end, execute the callback,Execute another round of macro tasksTo print
setTimeout2
, macro task complete
conclusion
This article focuses on the browser and Node environment event loop, we know what are macro tasks and micro tasks, also know the browser environment and Node environment macro tasks and micro tasks, through a set of interview questions, consolidate the task queue execution order