let’s think

Event loop in JS

What is the

JavaScript is designed to be single-threaded. When your program is running, there’s only one thread that can do one thing at a time.

why? Why do we do this, because it has to do with how JavaScript is used

JavaScript started out as a browser scripting language, usually manipulating the DOM. If it’s multi-threaded, one thread adds the DOM and one thread removes it, how does the browser work?

To solve the problem of single-threaded execution blocking,JavaScript uses a mechanism in computer systems called Event loops.

Event Loop

In JavaScript, all tasks can be categorized

  • Synchronization task: The synchronization task is executed immediately. The synchronization task is usually directly executed in the main thread
  • Asynchronous tasks: Tasks that are executed asynchronously, such as Ajax web requests, setTimeout timing functions, etc

The operation flow of synchronous and asynchronous tasks is as follows:

As can be seen from the above, synchronous tasks enter the main thread, that is, the main execution stack, and asynchronous tasks enter the task queue. After the execution of tasks in the main thread is completed, the corresponding tasks are read from the task queue and pushed to the main thread for execution. The repetition of the above process is the event loop.

Macro and micro tasks

If dividing tasks into synchronous and asynchronous tasks is not accurate, here is an example:

    console.log(1)
    setTimeout(() = >{
        console.log(2)},0)
    new Promise((resolve, reject) = >{
        console.log('new Promise')
        resolve()
    }).then(() = >{
        console.log('then')})console.log(3)
Copy the code

If we follow the above flowchart to analyze the code, we get the following execution steps:

Console. log(1), synchronization task, async task, async task, async task, async task, async task, async task, async task, async task Then, asynchronous tasks are placed in Event Table console.log(3), synchronous tasks are executed by the main thread, so according to analysis, The result should be 1=>'new Promise'=> 3 => 2 =>' then' but the actual result is: 1=>'new Promise'=> 3 =>' then' => 2Copy the code

The reason for the divergence is the order in which asynchronous tasks are executed. The event queue is actually a “first in, first out” data structure, and the first events are read by the main thread first

In this example, the setTimeout callback event is queued first and should be executed before.then, but the result is the opposite

The reason is that asynchronous tasks can be subdivided into microtasks and macro tasks

Micro tasks

A function that needs to be executed asynchronously, after the execution of the main function and before the completion of the current macro task

Common microtasks include:

  • Promise.then
  • MutaionObserver
  • Object. Observe (obsolete; Proxy object substitution)
  • Process. NextTick (Node. Js)

Macro task

The time granularity of macro tasks is relatively large, and the time interval of execution cannot be precisely controlled, which does not meet the requirements of high real-time performance

Common macro tasks are:

  • Script (can be understood as the outer synchronization code)
  • setTimeout/setInterval
  • The UI rendering/UI events
  • PostMessage, MessageChannel
  • SetImmediate, I/O (Node.js)

At this point, the relationship between event loops, macro tasks, and micro tasks is shown in the figure below

According to this process, its execution mechanism is:

  • Execute a macro task, and if it encounters a microtask, place it in the event queue of the microtask
  • After the current macro task is executed, the event queue of the microtask is viewed and all the microtasks in it are executed in sequence
  • And then I’m going to look at the macro task queue, and then I’m going to look at the micro task queue, and if I don’t look at the macro task queue, I’m done

A function that needs to be executed asynchronously, after the execution of the main function and before the completion of the current macro task

Async and await

‘async’ means’ async ‘and’ await ‘means’ waiting’

“Async” is used to declare an asynchronous method and “await” is used to wait for an asynchronous method to execute

async

The async function returns a Promise object. The following two methods are equivalent

    function f() {
        return Promise.resolve('TEST');
    }
    // asyncF is equivalent to f!
    async function asyncFn(){
        return 'TEST'
    }
Copy the code

await

Normally, the await command is followed by a Promise object that returns the result of that object. If it is not a Promise object, the corresponding value is returned

    async function f(){
        / / is equivalent to
        // return 123
        return await 123
    }
    f().then(v= > console.log(v)) / / 123
Copy the code

“Await” blocks the following code regardless of what is followed by “await”

    async function fn1 (){
        console.log(1)
        await fn2()
        console.log(2) / / blocking
    }

    async function fn2(){
        console.log('fn2')
    }

    fn1()
    console.log('fn2')
Copy the code

In the example above, await will block the following code (i.e. join microtask queue), execute the synchronous code outside async, finish the synchronous code, return to async function, and execute the blocked code

So the above output results are: 1, fn2, 3,2

Process analysis

From the above, we have a general idea of the order in which JavaScript executes various scenarios


    async function async1() {
        console.log('async1 start')
        await async2()
        console.log('async1 end')}async function async2() {
        console.log('async2')}console.log('script start')
    setTimeout(function () {
        console.log('settimeout')
    })
    async1()
    new Promise(function (resolve) {
        console.log('promise1')
        resolve()
    }).then(function () {
        console.log('promise2')})console.log('script end')

Copy the code

Analysis process:

  1. Log (‘script start’) to print the result and print script start
  2. Timer encountered, it is a macro task, put first do not execute
  3. If async1() is encountered, execute async1 and print async1 start. Execute async2, print async2, then block the following code (join the list of microtasks) and jump out to execute the synchronous code
  4. Go to New Promise, execute it, print promise1, and then.then(), which is a microtask, put it on the list of microtasks to be executed
  5. The last line prints script end directly, now that the synchronous code is finished, start the microtask, i.e. the code below await, prints async1 end
  6. Proceed to the next microtask, which is to perform a callback to THEN and print promisE2
  7. Now that everything is done for the last macro task, start the next macro task, which is the timer, print setTimeout

So the final result is: script start, async1 start, Async2, promise1, script end, AsynC1 end, promise2, setTimeout