Recently, one of my friends wrote that I encountered some problems related to Event Loop in the interview, mainly about the execution sequence of Promise and setTimeout when they appear together.

Some of my friends encountered a complex and deeply nested example code and failed to give a correct answer when asked about the principle.

I read some online articles, seem to be able to explain the example code execution, but always felt that there was something not quite right, then spent three days time to read some articles and video, mainly to read the SPEC of the Event Loops section, hope to have a more reasonable conclusion, this article is the summary of the I do according to these data, My level is limited, so I will not go into details, the summary may not be correct.

If you want to know more, you can study the resources at the end of this article.

Cognitive mistakes

Before we can officially enter the event cycle, we need to correct some of our past cognitive errors.

Macro and micro tasks

Many JavaScript tasks are divided into macro tasks and microtasks. However, there is no macro task in the SPEC. The SPEC refers to tasks and microtasks, and microtasks are colloquial terms. Refers to the task created by the microtask queuing algorithm.

This is the first cognitive error.

Macro task queue

The SPEC does not have a macro task queue, so there is no macro task queue. The SPEC uses task queues and microtask queues.

Queues are not real Queues, but ordered Sets. Microtask Queues are real Queues.

Many articles refer to setTimeout as a macro task, which enlists its callback function in the macro task queue, resulting in delayed code execution. In fact, we can easily verify this statement to know that there is a problem, for example:

console.log(`I will execute immediately! `);

setTimeout(function () {
  console.log(`I will execute after 6 seconds! `)},6000);

setTimeout(function () {
  console.log(`I will execute after 4 seconds! `)},4000);

setTimeout(function () {
  console.log(`I will execute after console.log! `)},0)
Copy the code

The results are as follows:

I will execute immediately!
I will execute after console.log!
I will execute after 4 seconds!
I will execute after 6 seconds!
Copy the code

We know that the normal order of execution for a queue would be first in, first out, and what we get from the print result is that the callback functions are queued in order, but not queued out.

So, remember that a task queue is not a queue, it’s an ordered set, and that’s the second cognitive error.

specification

The SPEC specification is quite extensive, and this article will briefly cover some of the highlights.

SPEC divides event loops into three categories: Window Event loop, worker Event loop, and Worklet event loop. Worker event loop is related to Web workers. I don’t know what the worklet Event loop is at the moment. If you are interested, you can study it. This article mainly explains window Event Loop.

Each event loop has one or more task queues. A task queue is a collection of tasks. There is only one microtask queue per event loop.

To understand event loops, you first need to know what a task is, what a task queue is, what a microtask is, and what a microtask queue is.

task

A task is defined in the SPEC specification as a structure that has the following.

Source is very important because it identifies the source of a task, namely task source. The user agent uses it to distinguish different types of tasks and then selects which task queue to add them to, which will be explained in detail later.

Steps specifies what to do in each step of the task.

Tasks encapsulate algorithms responsible for:

As you can see, how callback functions, asynchronously retrieving resources, etc., are handled is predetermined by the task algorithm, not by “queue in and queue out”.

Task source

There are six task sources in the SPEC (I’ve only found six so far), as shown in the following table:

Task source describe
timer task source Timer related tasks, such assetTimeout()
DOM manipulation task source Tasks related to DOM manipulation, such asnon-blockingMethod to insert elements into the document
user interaction task source Tasks related to user interaction, such asonclick()
networking task source Tasks related to network activities such asnew XMLHttpRequest()
history traversal task source Tasks related to browser history, such ashistory.back()
microtask task source Microtasks, such asPromise.then()

Task source is the basis of task queuing.

Task queue

The event loop will append different types of tasks to the corresponding task queue according to the task source, and then select tasks from the task queue for processing.

Since the specification says that there are one or more task queues per event loop, it means that the various task queues do not always exist at first and should be created as needed.

Microtasks and microtask queues

The concept of microtasks and microtask queues has been discussed previously. What kind of tasks are microtasks? MutationObserver(), Promise.then(), and promise.catch () are microtasks that will be added to the enqueue according to the SPEC and ECMA specifications. Dequeue from microtask queue after processing.

It is possible for microtasks to be moved to the regular task queue, as detailed in the specification.

summary

The SPEC also mentions an Element task in the Queue Tasks section, which is a slightly different class of DOM element tasks.

For example, when text is selected in textarea elements, the task source is the User Interaction Task source; The task source is DOM Manipulation Task Source if the IFrame element does not specify a SRC attribute and the user agent happens to be processing the iframe element’s attribute for the first time.

At this point, we can see that the queue “structure” in an event loop might look something like this:

Again, a task queue is not a queue, it’s an ordered collection, so keep that in mind.

If you want to know exactly how these tasks are queued, you can refer to the specification, which will not be expanded in this article.

Next we’ll look at the processing model of the event loop, which is the process of processing a task.

Processing model

The process is described in great detail in the SPEC specification, so let’s simplify it here, as shown below:

It is important to note that the specification does not specify in what order the task queues are selected, but rather in implementation-defined fashion, leaving implementation details up to the user agent, which is one of the reasons for the browser differences.

Each run of a task in the event loop removes it from the corresponding task queue, and each run of a microtask dequeue it from the microtask queue.

The processing process of worker event loop will be slightly different. For details, please refer to the specification.

One further detail is that IndexedDB transactions are cleared at the end of the microtask queue.

Rotation event cycle

Another important thing in the specification is the spin the event loop.

Well, I don’t really know what I’m talking about, but it might have something to do with the details of how the algorithm handles callbacks like setTimeout.

The sample

No matter how much theory is talked about, it still feels empty. Next, we will analyze it with examples.

The sample a

Modify the example at the beginning of the article slightly, paying special attention to the script tag:

<script>
  console.log(`I will execute immediately! `);

  setTimeout(function () {
    console.log(`I will execute after 6 seconds! `)},6000);

  setTimeout(function () {
    console.log(`I will execute after 4 seconds! `)},4000);

  setTimeout(function () {
    console.log(`I will execute after console.log! `)},0)
</script>
Copy the code

As mentioned earlier, the event loop starts by selecting a task queue, and the task queue is also created. When is the task queue created? The first time you run your JavaScript code.

Script is described at length in the specification. Here is a brief description of the process:

  1. To obtainscripts
  2. createscripts
  3. runscripts

So, in this example, the first console.log is printed when scripts are run, and all subsequent setTimeout are sequentially entered into the Timer Task Queue (presumably the task queue created when the first setTimeout is encountered), and then the event loop, Output in sequence.

The following output is displayed:

I will execute immediately!
I will execute after console.log!
I will execute after 4 seconds!
I will execute after 6 seconds!
Copy the code

The main purpose of this example is to give you an idea of when the event loop starts.

Example 2

Let’s modify the above example to include microtasks:

<script>
  console.log(`I will execute immediately! `);

  setTimeout(function () {
    console.log(`I will execute after 6 seconds! `)},6000);

  setTimeout(function () {
    console.log(`I will execute after 4 seconds! `)},4000);

  setTimeout(function () {
    console.log(`I will execute after console.log! `)},0);

  const promise = new Promise((resolve, reject) = > {
    console.log(1);
    resolve('success');
    console.log(2);
  });

  promise.then((a)= > {
    console.log(3);
  });
</script>
Copy the code

Based on past experience, I believe you can tell the answer as follows:

I will execute immediately!
1
2
3
I will execute after console.log!
I will execute after 4 seconds!
I will execute after 6 seconds!

Copy the code

But that’s not the point of this example.

If you follow the event loop flow described above:

  1. runconsole.log()
  2. setTimeoutIn turn intotimer task queue
  3. runnew Promise()In theconsole.log()
  4. promise.then()Enter the microtask queue
  5. runtimer task queueThe first task in printI will execute after console.log!
  6. Run the microtask queue, print3
  7. cycle

3 should be executed in I will execute after console.log! The later output, because it is a microtask, should be run after the first task, but it comes first.

This is because scripts are cleaned up after running, and an important part of the cleaning process is to run the microtask queue once before the event loop, So 3 will execute after console.log! Before the output.

Example 3

Let’s make a few more changes to the above example:

<script>
  console.log(`I will execute immediately! `);

  setTimeout(function () {
    console.log(`I will execute after 6 seconds! `)},6000);

  setTimeout(function () {
    console.log(`I will execute after 4 seconds! `)},4000);

  setTimeout(function () {
    console.log(`I will execute after console.log! `)},0);

  const promise = new Promise((resolve, reject) = > {
    console.log(1);
    resolve('success');
    console.log(2);
  });

  promise.then((a)= > {
    console.log(3);
  });
</script>
<script>
  console.log(`I will execute immediately! `);

  setTimeout(function () {
    console.log(`I will execute after 6 seconds! `)},6000);

  setTimeout(function () {
    console.log(`I will execute after 4 seconds! `)},4000);

  setTimeout(function () {
    console.log(`I will execute after console.log! `)},0);

  const promise2 = new Promise((resolve, reject) = > {
    console.log(1);
    resolve('success');
    console.log(2);
  });

  promise2.then((a)= > {
    console.log(3);
  });
</script>
Copy the code

What happens when you have multiple script tags? The results are as follows:

I will execute immediately!
1
2
3
I will execute immediately!
1
2
3
I will execute after console.log!
I will execute after console.log!
I will execute after 4 seconds!
I will execute after 4 seconds!
I will execute after 6 seconds!
I will execute after 6 seconds!
Copy the code

As you can see, JavaScript will run all script tags before the event loop.

conclusion

The event loop is a very complex thing, and this article has only scratched the surface. If you really want to understand the event loop, you should at least read the SPEC in its entirety, because it’s all connected.

Another important point is that specifications are constantly changing.

Welcome interested friends to discuss and correct the content of the article, after all, I still do not fully understand many details.

Finally, thanks to the authors of the resources, otherwise I would have had a nervous breakdown just reading the spec: Joy:

References:

  1. Further Adventures of the Event Loop – Erin Zimmer – JSConf EU 2018

  2. What the heck is the event loop anyway? | Philip Roberts | JSConf EU

  3. Tasks, microtasks, queues and schedules

  4. SPEC