One, the introduction

As we all know, JS is a single-threaded, asynchronous, non-blocking programming language. So how can a single thread be asynchronous?

Single-threaded vs. asynchronous

In JS, only one thread processes JS tasks, which means that all tasks can only be executed synchronously. If a network request is encountered, it must wait for the result of the request to return before JS can proceed. This is obviously implausible. So the Event Loop mechanism was introduced to help browsers deal with long-pending tasks.

Browser event loop model

This article does not involve the actual operating environment of the browser, but abstracts the event loop model of the browser.

Conceptual understanding

  1. The call stack(Call stack): the unique worker thread of the JS engine, used for function call execution
  2. WebApisBrowser-provided events such as DOM operations, AJAX requests, timers, etc
  3. Task queue(task queue)The: event loop queues the completed webApi events sequentially, and when the call stack is empty, the callback function at the top of the queue is pushed onto the call stack for execution
  4. Microtask queue(microtask queue): Each microtask in the microtask queue is executed in turn each time a task exits and the call stack is empty. The difference is that it waits until the microtask queue is empty before stopping execution — even if a microtask joins in mid-stream. In other words, a microtask can add new microtasks to the queue and complete all of the microtasks before the next task starts and the current event loop ends.

Event loop flow (not including rendering)

  1. The call stack is empty after all tasks on the call stack are executed.
  2. performmicrotask queueAll of themicrotask“, even if they join in the middlemicrotask; Such asmicrotask queueIf no, go to 3.
  3. performTask QueueThe company firsttask; If the call stack is empty, go to 2.

For example

<script>
      function task1() {
        console.log('task1')
        task2()
        return
      }
      function task2() {
        console.log('task2')
        task3()
        return
      }
      function task3() {
        console.log('task3')
        return
      }
      document.body.addEventListener('click'.() = > {
        setTimeout(function handleClick(){
          console.log('handleClick called')})})new Promise((resolve) = > {
        document.body.addEventListener('click'.() = > {
          resolve('promise')
        })
      }).then(function success(value) {
        console.log(value)
      })

      task1()
      document.body.click()
</script>
Copy the code
  1. The main functionmainPush the call stack, code is executed from top to bottom, function declarationtask1,task2,task3;
  2. addbodytheclickEvent handlerhandleClickGo to WebAPIs and wait for the event to fire.
  3. addpromisewhenbodytheclickEvent triggered when calledresolve('promise');
  4. task1The call,task1Push call stack execution, console output'task1';
  5. task1Internal callstask2.task2Push call stack execution, console output'task2';
  6. task2Internal callstask3.task3Push call stack execution, console output'task3';
  7. task3After execution, return and pop up call stack;
  8. task2After execution, return and pop up call stack;
  9. task1After execution, return and pop up call stack;
  10. document.body.click().clickEvent firing, listening to function execution;
  11. handleClickpushtask queueWaiting for execution;resolveThe call,successpushmaricotask queueWaiting for execution;
  12. mainAfter execution, return and pop up call stack; Call stack empty;
  13. The event loop mechanism checks that the call stack is empty and executesmicrotask queueThe callback function insuccess, console printing'promise';
  14. checkmicrotask queueIf no, executetask queueThe callback function inhandleClick, console printing'handleClick called';