Macro and micro tasks

In addition to the macrotasks described in this chapter, there are also microtasks mentioned in the chapter MicroTasks.

Microtasks only come from our code. They are typically created by promise: the execution of a.then/catch/finally handler becomes a microtask. Microtasks are also used “behind the scenes” of await, as it is another form of promise processing.

There is also a special function, queueMicrotask(func), which queues func for execution in a microtask queue.

After each macro task, the engine immediately executes all tasks in the microtask queue, and then executes any other macro task, or render, or anything else.

For example, consider the following example:

setTimeout(() = > alert("timeout"));

Promise.resolve()
  .then(() = > alert("promise"));

alert("code");
Copy the code

What is the order of execution here?

  1. codeDisplay first, because it is a regular synchronous call.
  2. promiseThe second one appears becausethenWill pass through the microtask queue and execute after the current code.
  3. timeoutDisplay last because it is a macro task.

A more detailed loop of events is shown below (from top to bottom, i.e., scripting first, then microtasks, rendering, etc.) :

The microtask is completed before performing any other event processing, or rendering, or performing any other macro task.

This is important because it ensures that the application environment is essentially the same between microtasks (no mouse coordinate changes, no new network data, and so on).

If we want to execute a function asynchronously (after the current code), but before changes are rendered or new events are processed, we can schedule it using queueMicrotask.

This is a similar example to the previous one with a “count progress bar”, but it uses queueMicrotask instead of setTimeout. You can see it rendering at the end. As if writing synchronous code:

<div id="progress"></div>

<script>
  let i = 0;

  function count() {

    // Do part of the heavy task (*)
    do {
      i++;
      progress.innerHTML = i;
    } while (i % 1e3! =0);

    if (i < 1e6) {
queueMicrotask(count);
    }

  }

  count();
</script>
Copy the code

conclusion

A more detailed event loop algorithm (though still simplified compared to the specification) :

  1. Dequeue from a macro task queue (such as “script”) and execute the earliest task.

  2. Perform all microtasks:

    • When the microtask queue is not empty:

      • Dequeue and perform the earliest microtask.
  3. If there are changes, render them.

  4. If the macro task queue is empty, sleep until the macro task appears.

  5. Go to Step 1.

Schedule a new macro task:

  • Use zero delaysetTimeout(f).

It can be used to break a heavy computing task into parts so that the browser can react to user events and display the progress of the task between the parts of the task.

In addition, it is also used in event handlers to schedule an action after the event has been fully processed.

Schedule a new microtask:

  • usequeueMicrotask(f).
  • The Promise handler also passes through the microtask queue.

There is no PROCESSING of UI or network events between microtasks: they are executed immediately, one after the other.

So, we can use queueMicrotask to execute a function asynchronously while keeping the state of the environment consistent.

Web Workers

For long, heavy computing tasks that should not block event loops, we can use Web Workers

This is how you run code in another parallel thread.

Web Workers can exchange messages with the main thread, but they have their own variables and event loops.

Web Workers do not have DOM access, so they are useful for computations that use multiple CPU cores at the same time.


Reprint details:

Reprint site: javascript.info

Reprint address: zh.javascript.info/event-loop