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:

  • asyncasynchronousAsynchronous, asynchronous, asynchronous
  • syncsynchronousSynchrony, 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 programs
  • Thread (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 callsetTimeoutThis 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 completesetTimeoutThe 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 examplesetTimeoutThe 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, printpromise1
  • Execute line 27 print, print2
  • 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, printingthen1
  • Because queueMicrotask on line 29 is a microtask, print itqueueMicrotask1
  • Because line 36 promise. then is a microtask, executed before setTimeout on line 1, printingthen3.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, printsetTimeout1
  • 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, printthen2.End of synchronization code
  • Start emptying the microtask queue: Execute promise. then on line 9, printthen4.The microtask queue ends
  • The macro task starts: Perform the macro task in line 23 to printsetTimeout2
  • 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 linesscript start
  • Execute async1 to print line 2async1 start
  • Execute async2 and print line 8async2
  • The executor that executes the New Promise on line 19, printspromise1.End of synchronization code
  • Start clearing microtasks: Execute line 4 to printasync1 end
  • Go to line 23, printpromise2.The microtask queue is cleared
  • Start emptying macro tasks: Executes the callback passed in by setTimeout on line 13, printingsetTimeout.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 executedsetTimeout()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 assocket.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 nextTick
    • other queue: promise. then callback, queueMicrotask
  • Macro task queue:
    • timer queue: setTimeout and setInterval
    • poll queue: IO events
    • check queue: setImmediate
    • close 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, printscript start
  • Line 25 async 1, line 2, printasync 1 start
  • Await line 3 async2, line 8 printasync 2The code in line 4 is a callback to promise.then, so it will not be executed now
  • Print line 30promise1To print line 32promise2
  • Print on line 37script end.The main script to complete
  • Start emptying the microtask queue: Execute line 23 in the order described in 4.2 to printnextTick1
  • Go to line 27, printnextTick2
  • Go to line 4, printasync1 end
  • Go to line 34, printpromise3.Microtask complete
  • The macro task queue starts to clear: Run the timer in line 17 to printsetTimeout0
  • Go to line 21, printsetImmediate.Macro task completed
  • Wait for the timer on line 17 to end, execute the callback,Execute another round of macro tasksTo printsetTimeout2, 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