草案
此页面不完整。

This page is very much a work in progress; it contains technical details that may be useful while considering using—and while using—microtasks, but it is not absolutely necessary for most people to know. Additionally, there may be errors here as this draft is just that rough. Sheppy

When debugging or, possibly, when trying to decide upon the best approach to solving a problem around timing and scheduling of tasks and microtasks, there are things about how the JavaScript runtime operates under the hood that may be useful to understand. That's what this section covers

介绍

JavaScript is an inherently single-threaded language. It was designed in an era in which this was a positive choice; there were few multi-processor computers available to the general public, and the expected amount of code that would be handled by JavaScript was relatively low at that time.

As time passed, of course, we know that computers have evolved into powerful multi-core systems, and JavaScript has become one of the most prolifically-used languages in the computing world. A vast number of the most popular applications are based at least in part on JavaScript code. To support this, it was necessary to find ways to allow for projects to escape the limitations of a single-threaded language.

Starting with the addition of timeouts and intervals as part of the Web API ( setTimeout() and setInterval() ), the JavaScript environment provided by Web browsers has gradually advanced to include powerful features that enable scheduling of tasks, multi-threaded application development, and so forth. To understand where queueMicrotask() comes into play here, it's helpful to understand how the JavaScript runtime operates when scheduling and running code.

JavaScript execution contexts

注意: The details here are generally not important to most JavaScript programmers. This information is provided as a basis for why microtasks are useful and how they function; if you don't care, you can skip this and come back later if you find that you need to.

When a fragment of JavaScript code runs, it runs inside an execution context . There are three types of code that create a new execution context:

  • The global context is the execution context created to run the main body of your code; that is, any code that exists outside of a JavaScript function.
  • Each function is run within its own execution context. This is frequently referred to as a "local context."
  • Using the ill-advised eval() function also creates a new execution context.

Each context is, in essence, a level of scope within your code. As one of these code segments begins execution, a new context is constructed in which to run it; that context is then destroyed when the code exits. Consider the JavaScript program below:

let outputElem = document.getElementById("output");
let userLanguages = {
  "Mike": "en",
  "Teresa": "es"
};
function greetUser(user) {
  function localGreeting(user) {
    let greeting;
    let language = userLanguages[user];
    switch(language) {
      case "es":
        greeting = `¡Hola, ${user}!`;
        break;
      case "en":
      默认:
        greeting = `Hello, ${user}!`;
        break;
    }
    return greeting;
  }
  outputElem.innerHTML += localGreeting(user) + "<br>\r";
}
greetUser("Mike");
greetUser("Teresa");
greetUser("Veronica");
					

This short program contains three execution contexts, some of which are created and destroyed several times over the course of the program's execution. As each context is created, it is placed on the execution context stack . When it exits, the context is removed from the context stack.

  • Upon starting the program, the global context is created.
    • greetUser("Mike") is reached, a context is created for the greetUser() function; this execution context is pushed onto the execution context stack.
      • greetUser() 调用 localGreeting() , another context is created to run that function. When this function returns, the context for localGreeting() is removed from the execution stack and destroyed. Program execution resumes with the next context found on the stack, which is greetUser() ; this function resumes execution where it left off.
      • greetUser() function returns and its context is removed from the stack and destroyed.
    • greetUser("Teresa") is reached, a context is created for it and pushed onto the stack.
      • greetUser() 调用 localGreeting() , another context is created to run that function. When this function returns, the context for localGreeting() is removed from the execution stack and destroyed. greetUser() continues to execute where it left off.
      • greetUser() function returns and its context is removed from the stack and destroyed.
    • greetUser("Veronica") is reached, a context is created for it and pushed onto the stack.
      • greetUser() 调用 localGreeting() , another context is created to run that function. When this function returns, the context for localGreeting() is removed from the execution stack and destroyed.
      • greetUser() function returns and its context is removed from the stack and destroyed.
  • The main program exits and its execution context is removed from the execution stack; as there are no contexts remaining on the stack, program execution ends.

Using execution contexts in this manner, each program and function is able to have its own set of variables and other objects. Each context additionally tracks the next line in the program that should be run and other information critical to that context's operation. By using the contexts and the context stack in this manner, many of the fundamentals of how a program operates can be managed, including local and global variables, function calls and returns, and so forth.

A special note about recursive functions—that is, functions which call themselves, possibly over multiple levels of depth or recursion: each recursive call to the function creates a new execution context. This allows the JavaScript runtime to track the levels of recursion and the return of results through that recursion, but it also means that each time a function recurses, more memory is needed to create the new context.

Run, JavaScript, run

To run JavaScript code, the runtime engine maintains a set of agents in which to execute JavaScript code. Each agent is made up of a set of execution contexts, the execution context stack, a main thread, a set for any additional threads that may be created to handle workers, a task queue, and a microtask queue. Other than the main thread—which some browsers share across multiple agents—each component of an agent is unique to that agent.

Here we look at how the runtime functions in slightly more detail.

Event loops

Each agent is driven by an 事件循环 , which collects any user and other events, enqueuing tasks to handle each callback. It then runs any pending JavaScript tasks, then any pending microtasks, then performs any needed rendering and painting before looping again to check for pending tasks.

Your web site or app's code runs in the same thread , sharing the same 事件循环 , as the user interface of the web browser itself. This is the main thread , and in addition to running your site's main code body, it handles receiving and dispatching user and other events, rendering and painting web content, and so forth.

The event loop, then, drives everything that happens in the browser as it pertains to the interaction with the user, but more importantly for our purposes here, it is responsible for the scheduling and execution of every piece of code that runs within its thread.

There are three types of event loop:

Window event loop

The window event loop is the one that drives all of the windows sharing a similar origin (though there are further limits to this as described elsewhere in this article XXXX ????).

Worker event loop
A worker event loop is one which drives a worker; this includes all forms of workers, including basic web workers , shared workers ,和 service workers . Workers are kept in one or more agents that are separate from the "main" code; the browser may use a single event loop for all of the workers of a given type or may use multiple event loops to handle them.
Worklet event loop
A worklet event loop is the event loop used to drive agents which run the code for the workelets for a given agent. This includes worklets of type Worklet , AudioWorklet ,和 PaintWorklet .

Several windows loaded from the same origin may be running on the same event loop, each queueing tasks onto the event loop so that their tasks take turns with the processor, one after another. Keep in mind that web parlance, the word "window" actually means "browser-level container that web content runs within," including an actual window, a tab, or a frame.

There are specific circumstances in which this sharing of an event loop among windows with a common origin is possible, such as:

  • If one window opened the other window, they are likely to be sharing an event loop.
  • If a window is actually a container within an <iframe> , it likely shares an event loop with the window that contains it.
  • The windows happen to share the same process in a multi-process web browser implementation.

The specifics may vary from browser to browser, depending on how they're implemented.

Tasks vs microtasks

A task is any JavaScript scheduled to be run by the standard mechanisms such as initially starting to execute a program, an event triggering a callback, and so forth. Other than by using events, you can enqueue a task by using setTimeout() or setInterval() .

The difference between the task queue and the microtask queue is simple but very important:

  • When executing tasks from the task queue, the runtime executes each task that is in the queue at the moment a new iteration of the event loop begins. Tasks added to the queue after the iteration begins will not run until the next iteration .
  • Each time a task exits, and the execution context stack is empty, each microtask in the microtask queue is executed, one after another. The difference is that execution of microtasks continues until the queue is empty—even if new ones are scheduled in the interim. In other words, microtasks can enqueue new microtasks and those new microtasks will execute before the next task begins to run, and before the end of the current event loop iteration.

Problems

Because your code runs in the same thread, using the same event loop, as the browser's user interface, if your code blocks or enters an infinite loop, the browser itself will stall. Even sluggish performance, whether caused by a bug or because of complex work being done by your code, can cause the user to suffer a sluggish browser.

When multiple programs and multiple code objects within those programs start to try to work at once, alongside a browser which also needs processor time—let alone time to render and draw the site and its own UI, handle user events, and so forth—everything becomes clogged up far too easily nowadays.

解决方案

The use of web workers , which allow the main script to run other scripts in new threads, help to alleviate this problem. A well-designed web site or app uses workers to perform any complex or lengthy operations, leaving the main thread to do as little work as possible beyond updating, laying out, and rendering the web page.

This is further alleviated by using asynchronous JavaScript techniques such as promises to allow the main code to continue to run while waiting for the results of a request. However, code running at a more fundamental level—such as code comprising a library or framework—may need a way to schedule code to be run at a safe time while still executing on the main thread, independent of the results of any single request or task.

Microtasks are another solution to this problem, providing a finer degree of access by making it possible to schedule code to run before the next iteration of the event loop begins, instead of having to wait until the next one.

The microtask queue has been around for a while, but it's historiclaly been used only internally in order to drive things like promises. The addition of queueMicrotask() , exposing it to web developers, creates a unified queue for microtasks which is used wherever it's necessary to have the ability to schedule code to run safely when there are no execution contexts left on the JavaScript execution context stack. Across multiple instances and across all browsers and JavaScript runtimes, a standardized microqueue mechanism means these microtasks will operate reliably in the same order, thus avoiding potentially difficult to find bugs.

另请参阅

元数据

  • 最后修改:
  1. HTML_DOM_API
  2. HTML DOM 相关页面
    1. BeforeUnloadEvent
    2. DOMStringMap
    3. ErrorEvent
    4. GlobalEventHandlers
    5. HTMLAnchorElement
    6. HTMLAreaElement
    7. HTMLAudioElement
    8. HTMLBRElement
    9. HTMLBaseElement
    10. HTMLBaseFontElement
    11. HTMLBodyElement
    12. HTMLButtonElement
    13. HTMLCanvasElement
    14. HTMLContentElement
    15. HTMLDListElement
    16. HTMLDataElement
    17. HTMLDataListElement
    18. HTMLDialogElement
    19. HTMLDivElement
    20. HTMLDocument
    21. HTMLElement
    22. HTMLEmbedElement
    23. HTMLFieldSetElement
    24. HTMLFormControlsCollection
    25. HTMLFormElement
    26. HTMLFrameSetElement
    27. HTMLHRElement
    28. HTMLHeadElement
    29. HTMLHeadingElement
    30. HTMLHtmlElement
    31. HTMLIFrameElement
    32. HTMLImageElement
    33. HTMLInputElement
    34. HTMLIsIndexElement
    35. HTMLKeygenElement
    36. HTMLLIElement
    37. HTMLLabelElement
    38. HTMLLegendElement
    39. HTMLLinkElement
    40. HTMLMapElement
    41. HTMLMediaElement
    42. HTMLMetaElement
    43. HTMLMeterElement
    44. HTMLModElement
    45. HTMLOListElement
    46. HTMLObjectElement
    47. HTMLOptGroupElement
    48. HTMLOptionElement
    49. HTMLOptionsCollection
    50. HTMLOutputElement
    51. HTMLParagraphElement
    52. HTMLParamElement
    53. HTMLPictureElement
    54. HTMLPreElement
    55. HTMLProgressElement
    56. HTMLQuoteElement
    57. HTMLScriptElement
    58. HTMLSelectElement
    59. HTMLShadowElement
    60. HTMLSourceElement
    61. HTMLSpanElement
    62. HTMLStyleElement
    63. HTMLTableCaptionElement
    64. HTMLTableCellElement
    65. HTMLTableColElement
    66. HTMLTableDataCellElement
    67. HTMLTableElement
    68. HTMLTableHeaderCellElement
    69. HTMLTableRowElement
    70. HTMLTableSectionElement
    71. HTMLTemplateElement
    72. HTMLTextAreaElement
    73. HTMLTimeElement
    74. HTMLTitleElement
    75. HTMLTrackElement
    76. HTMLUListElement
    77. HTMLUnknownElement
    78. HTMLVideoElement
    79. HashChangeEvent
    80. 历史
    81. ImageData
    82. 定位
    83. MessageChannel
    84. MessageEvent
    85. MessagePort
    86. Navigator
    87. NavigatorGeolocation
    88. NavigatorID
    89. NavigatorLanguage
    90. NavigatorOnLine
    91. NavigatorPlugins
    92. PageTransitionEvent
    93. Plugin
    94. PluginArray
    95. PopStateEvent
    96. PortCollection
    97. PromiseRejectionEvent
    98. RadioNodeList
    99. Transferable
    100. ValidityState
    101. Window
    102. WindowBase64
    103. WindowEventHandlers
    104. WindowTimers