aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/web/api/html_dom_api/microtask_guide
diff options
context:
space:
mode:
authorPeter Bengtsson <mail@peterbe.com>2020-12-08 14:40:17 -0500
committerPeter Bengtsson <mail@peterbe.com>2020-12-08 14:40:17 -0500
commit33058f2b292b3a581333bdfb21b8f671898c5060 (patch)
tree51c3e392513ec574331b2d3f85c394445ea803c6 /files/zh-cn/web/api/html_dom_api/microtask_guide
parent8b66d724f7caf0157093fb09cfec8fbd0c6ad50a (diff)
downloadtranslated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.gz
translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.bz2
translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.zip
initial commit
Diffstat (limited to 'files/zh-cn/web/api/html_dom_api/microtask_guide')
-rw-r--r--files/zh-cn/web/api/html_dom_api/microtask_guide/in_depth/index.html185
-rw-r--r--files/zh-cn/web/api/html_dom_api/microtask_guide/index.html315
2 files changed, 500 insertions, 0 deletions
diff --git a/files/zh-cn/web/api/html_dom_api/microtask_guide/in_depth/index.html b/files/zh-cn/web/api/html_dom_api/microtask_guide/in_depth/index.html
new file mode 100644
index 0000000000..14e1139cfc
--- /dev/null
+++ b/files/zh-cn/web/api/html_dom_api/microtask_guide/in_depth/index.html
@@ -0,0 +1,185 @@
+---
+title: 深入:微任务与Javascript运行时环境
+slug: Web/API/HTML_DOM_API/Microtask_guide/In_depth
+tags:
+ - 异步
+ - 微任务
+ - 微任务队列
+ - 指南
+ - 运行时
+ - 高级
+translation_of: Web/API/HTML_DOM_API/Microtask_guide/In_depth
+---
+<p>{{APIRef("HTML DOM")}}{{Draft("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")}}</p>
+
+<p>当你在调试的时候,或者在决定如何以最佳的方式为任务队列和微任务队列安排调度顺序的时候,如果你了解关于 JavaScript 运行时是如何在背后执行这一切的,那将对你的理解会非常有帮助。这就是本节涵盖的内容。</p>
+
+<h2 id="介绍">介绍</h2>
+
+<p>JavaScript 本质上是一门单线程语言。对于在它被设计出来的那个年代来说,这样的设计是一个很好的选择。那个时候的人们很少有多处理器计算机,并且当时也只是打算用 JavaScript 来编写少量的代码。</p>
+
+<p>当然,随着时间的流逝,电脑的性能得到了飞速的提升。JavaScript 也变成了众多流行语言中的一员。许多非常受欢迎的应用或多或少都有 JavaScript 的影子。为此,找到一种可以突破传统单线程语言限制的方法变得很有必要。</p>
+
+<p>自从 ({{domxref("window.setTimeout", "setTimeout()")}} 和 {{domxref("window.setInterval", "setInterval()")}}) 加入到 Web API 后,浏览器提供的 JavaScript 环境就已经逐渐开始包含了任务安排、多线程应用开发等强大的特性。了解 JavaScript 运行时是如何安排和运行代码的对了解 <code>queueMicrotask()</code> 会非常有作用。</p>
+
+<h2 id="JavaScript_执行上下文">JavaScript 执行上下文</h2>
+
+<div class="blockIndicator note">
+<p><strong>注意:</strong>对于大多数 JavaScript 开发人员来说,这些细节并不重要。这里提供的信息只用于了解为什么微任务非常有用以及它们是如何工作的。如果你并不关心这些内容,你可以跳过这部分或者在你觉得需要的时候再倒回来查看。</p>
+</div>
+
+<p>当一段 JavaScript 代码在运行的时候,它实际上是运行在<strong>执行上下文</strong>中。下面3种类型的代码会创建一个新的执行上下文:</p>
+
+<ul>
+ <li>全局上下文是为运行代码主体而创建的执行上下文,也就是说它是为那些存在于JavaScript 函数之外的任何代码而创建的。</li>
+ <li>每个函数会在执行的时候创建自己的执行上下文。这个上下文就是通常说的 “本地上下文”。</li>
+ <li>使用 {{jsxref("eval()")}} 函数也会创建一个新的执行上下文。</li>
+</ul>
+
+<p>每一个上下文在本质上都是一种作用域层级。每个代码段开始执行的时候都会创建一个新的上下文来运行它,并且在代码退出的时候销毁掉。看看下面这段 JavaScript 程序:</p>
+
+<pre class="brush: js notranslate">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":
+ default:
+ greeting = `Hello, ${user}!`;
+ break;
+ }
+ return greeting;
+ }
+ outputElem.innerHTML += localGreeting(user) + "&lt;br&gt;\r";
+}
+
+greetUser("Mike");
+greetUser("Teresa");
+greetUser("Veronica");</pre>
+
+<p>这段程序代码包含了三个执行上下文,其中有些会在程序运行的过程中多次创建和销毁。每个上下文创建的时候会被推入<strong>执行上下文栈</strong>。当退出的时候,它会从上下文栈中移除。</p>
+
+<ul>
+ <li>程序开始运行时,全局上下文就会被创建好。
+ <ul>
+ <li>当执行到 <code>greetUser("Mike")</code> 的时候会为 <code>greetUser()</code> 函数创建一个它的上下文。这个执行上下文会被推入执行上下文栈中。
+
+ <ul>
+ <li>当 <code>greetUser()</code> 调用 <code>localGreeting()</code>的时候会为该方法创建一个新的上下文。并且在 <code>localGreeting()</code> 退出的时候它的上下文也会从执行栈中弹出并销毁。 程序会从栈中获取下一个上下文并恢复执行, 也就是从 <code>greetUser()</code> 剩下的部分开始执行。</li>
+ <li><code>greetUser()</code> 执行完毕并退出,其上下文也从栈中弹出并销毁。</li>
+ </ul>
+ </li>
+ <li>当 <code>greetUser("Teresa")</code> 开始执行时,程序又会为它创建一个上下文并推入栈顶。
+ <ul>
+ <li>当 <code>greetUser()</code> 调用 <code>localGreeting()</code>的时候另一个上下文被创建并用于运行该函数。 当 <code>localGreeting()</code> 退出的时候它的上下文也从栈中弹出并销毁。 <code>greetUser()</code> 得到恢复并继续执行剩下的部分。</li>
+ <li><code>greetUser()</code> 执行完毕并退出,其上下文也从栈中弹出并销毁。</li>
+ </ul>
+ </li>
+ <li>然后执行到 <code>greetUser("Veronica")</code> 又再为它创建一个上下文并推入栈顶。
+ <ul>
+ <li>当 <code>greetUser()</code> 调用 <code>localGreeting()</code>的时候,另一个上下文被创建用于执行该函数。当 <code>localGreeting()</code>执行完毕,它的上下文也从栈中弹出并销毁。</li>
+ <li><code>greetUser()</code> 执行完毕退出,其上下文也从栈中弹出并销毁。</li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ <li>主程序退出,全局执行上下文从执行栈中弹出。此时栈中所有的上下文都已经弹出,程序执行完毕。</li>
+</ul>
+
+<p>以这种方式来使用执行上下文,使得每个程序和函数都能够拥有自己的变量和其他对象。每个上下文还能够额外的跟踪程序中下一行需要执行的代码以及一些对上下文非常重要的信息。以这种方式来使用上下文和上下文栈,使得我们可以对程序运行的一些基础部分进行管理,包括局部和全局变量、函数的调用与返回等。</p>
+
+<p>关于递归函数——即多次调用自身的函数,需要特别注意:每次递归调用自身都会创建一个新的上下文。这使得 JavaScript 运行时能够追踪递归的层级以及从递归中得到的返回值,但这也意味着每次递归都会消耗内存来创建新的上下文。</p>
+
+<h2 id="JavaScript运行时">JavaScript运行时</h2>
+
+<p>在执行 JavaScript 代码的时候,JavaScript 运行时实际上维护了一组用于执行 JavaScript 代码的<strong>代理</strong>。每个代理由一组执行上下文的集合、执行上下文栈、主线程、一组可能创建用于执行 worker 的额外的线程集合、一个任务队列以及一个微任务队列构成。除了主线程(某些浏览器在多个代理之间共享的主线程)之外,其它组成部分对该代理都是唯一的。</p>
+
+<p>现在我们来更加详细的了解一下运行时是如何工作的。</p>
+
+<ul>
+</ul>
+
+<h3 id="事件循环(Event_loops)">事件循环(Event loops)</h3>
+
+<p>每个代理都是由<strong>事件循环</strong>驱动的,事件循环负责收集用事件(包括用户事件以及其他非用户事件等)、对任务进行排队以便在合适的时候执行回调。然后它执行所有处于等待中的 JavaScript 任务(宏任务),然后是微任务,然后在开始下一次循环之前执行一些必要的渲染和绘制操作。</p>
+
+<p>网页或者 app 的代码和浏览器本身的用户界面程序运行在相同的 <strong>{{Glossary("线程")}}</strong>中, 共享相同的 <strong>事件循环</strong>。 该线程就是 <strong>{{Glossary("主线程")}}</strong>,它除了运行网页本身的代码之外,还负责收集和派发用户和其它事件,以及渲染和绘制网页内容等。</p>
+
+<p>然后,事件循环会驱动发生在浏览器中与用户交互有关的一切,但在这里,对我们来说更重要的是需要了解它是如何负责调度和执行在其线程中执行的每段代码的。</p>
+
+<p>有如下三种事件循环:</p>
+
+<dl>
+ <dt>Window 事件循环</dt>
+ <dd>window 事件循环驱动所有同源的窗口 (though there are further limits to this as described elsewhere in this article XXXX ????).</dd>
+ <dt>Worker 事件循环</dt>
+ <dd>worker 事件循环顾名思义就是驱动 worker 的事件循环。这包括了所有种类的 worker:最基本的 <a href="/en-US/docs/Web/API/Web_Workers_API">web worker</a> 以及 <a href="/en-US/docs/Web/API/SharedWorker">shared worker</a> 和 <a href="/en-US/docs/Web/API/Service_Worker_API">service worker</a>。 Worker 被放在一个或多个独立于 “主代码” 的代理中。浏览器可能会用单个或多个事件循环来处理给定类型的所有 workder。</dd>
+ <dt>Worklet 事件循环</dt>
+ <dd><a href="/en-US/docs/Web/API/Worklet">worklet</a> 事件循环用于驱动运行 worklet 的代理。这包含了 {{domxref("Worklet")}}、{{domxref("AudioWorklet")}} 以及 {{domxref("PaintWorklet")}}。</dd>
+</dl>
+
+<p>多个同{{Glossary("源")}}(译者注:此处同源的源应该不是指同源策略中的源,而是指由同一个窗口打开的多个子窗口或同一个窗口中的多个 iframe 等,意味着起源的意思,下一段内容就会对这里进行说明)窗口可能运行在相同的事件循环中,每个队列任务进入到事件循环中以便处理器能够轮流对它们进行处理。记住这里的网络术语 “window” 实际上指的用于运行网页内容的浏览器级容器,包括实际的 window,一个 tab 标签或者一个 frame。</p>
+
+<p>在特定情况下,同源窗口之间共享事件循环,例如:</p>
+
+<ul>
+ <li>如果一个窗口打开了另一个窗口,它们可能会共享一个事件循环。</li>
+ <li>如果窗口是包含在 {{HTMLElement("iframe")}} 中,则它可能会和包含它的窗口共享一个事件循环。</li>
+ <li>在多进程浏览器中多个窗口碰巧共享了同一个进程。</li>
+</ul>
+
+<p>这种特定情况依赖于浏览器的具体实现,各个浏览器可能并不一样。</p>
+
+<h4 id="任务_vs_微任务">任务 vs 微任务</h4>
+
+<p>一个<strong>任务</strong>就是指计划由标准机制来执行的任何 JavaScript,如程序的初始化、事件触发的回调等。 除了使用事件,你还可以使用 {{domxref("window.setTimeout", "setTimeout()")}} 或者 {{domxref("window.setInterval", "setInterval()")}} 来添加任务<strong>。</strong></p>
+
+<p>任务队列和微任务队列的区别很简单,但却很重要:</p>
+
+<ul>
+ <li>当执行来自任务队列中的任务时,在每一次新的事件循环开始迭代的时候运行时都会执行队列中的每个任务。在每次迭代开始之后加入到队列中的任务需要<strong>在下一次迭代开始之后才会被执行</strong>.</li>
+ <li>每次当一个任务退出且执行上下文为空的时候,微任务队列中的每一个微任务会依次被执行。不同的是它会等到微任务队列为空才会停止执行——即使中途有微任务加入。换句话说,微任务可以添加新的微任务到队列中,并在下一个任务开始执行之前且当前事件循环结束之前执行完所有的微任务。</li>
+</ul>
+
+<h3 id="问题">问题</h3>
+
+<p>由于你的代码和浏览器的用户界面运行在同一个线程中,共享同一个事件循环,假如你的代码阻塞了或者进入了无限循环,则浏览器将会卡死。无论是由于 bug 引起还是代码中进行复杂的运算导致的性能降低,都会降低用户的体验。</p>
+
+<p>当来自多个程序的多个代码对象尝试同时运行的时候,一切都可能变得很慢甚至被阻塞,更不要说浏览器还需要时间来渲染和绘制网站和 UI、处理用户事件等。</p>
+
+<h3 id="解决方案">解决方案</h3>
+
+<p>使用 <a href="/en-US/docs/Web/API/Web_Workers_API">web workers</a> 可以让主线程另起新的线程来运行脚本,这能够缓解上面的情况。一个设计良好的网站或应用会把一些复杂的或者耗时的操作交给 worker 去做,这样可以让主线程除了更新、布局和渲染网页之外,尽可能少的去做其他事情。</p>
+
+<p>通过使用像 <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">promises</a> 这样的 <a href="/en-US/docs/Learn/JavaScript/Asynchronous">异步JavaScript</a> 技术可以使得主线程在等待请求返回结果的同时继续往下执行,这能够更进一步减轻上面提到的情况。然而,一些更接近于基础功能的代码——比如一些框架代码,可能更需要将代码安排在主线程上一个安全的时间来运行,它与任何请求的结果或者任务无关。</p>
+
+<p>微任务是另一种解决该问题的方案,通过将代码安排在下一次事件循环开始之前运行而不是必须要等到下一次开始之后才执行,这样可以提供一个更好的访问级别。</p>
+
+<p>微任务队列已经存在有一段时间了,但之前它仅仅被内部使用来驱动诸如 promise 这些。<code>queueMicrotask()</code>的加入可以让开发者创建一个统一的微任务队列,它能够在任何时候即便是当 JavaScript 执行上下文栈中没有执行上下文剩余时也可以将代码安排在一个安全的时间运行。 在多个实例、所有浏览器以及运行时中,一个标准的微任务队列机都制意味着这些微任务可以非常可靠的以相同的顺序执行,从而避免一些潜在的难以发现的错误。</p>
+
+<h2 id="更多">更多</h2>
+
+<ul>
+ <li><a href="/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide">Microtask guide</a></li>
+ <li>{{domxref("WindowOrWorkerGlobalScope.queueMicrotask", "queueMicrotask()")}}</li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous">Asynchronous JavaScript</a>
+ <ul>
+ <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts">General asynchronous programming concepts</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">Introducing asynchronous JavaScript</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">Cooperative asynchronous JavaScript: Timeouts and intervals</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">Graceful asynchronous programming with Promises</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">Choosing the right approach</a></li>
+ </ul>
+ </li>
+</ul>
diff --git a/files/zh-cn/web/api/html_dom_api/microtask_guide/index.html b/files/zh-cn/web/api/html_dom_api/microtask_guide/index.html
new file mode 100644
index 0000000000..e2af5268a5
--- /dev/null
+++ b/files/zh-cn/web/api/html_dom_api/microtask_guide/index.html
@@ -0,0 +1,315 @@
+---
+title: 在 JavaScript 中通过 queueMicrotask() 使用微任务
+slug: Web/API/HTML_DOM_API/Microtask_guide
+translation_of: Web/API/HTML_DOM_API/Microtask_guide
+---
+<p>{{APIRef("HTML DOM")}}</p>
+
+<p><span class="seoSummary">一个 <strong>微任务(microtask)</strong>就是一个简短的函数,当创建该函数的函数执行之后,<em>并且</em> 只有当 Javascript 调用栈为空,而控制权尚未返还给被 {{Glossary("user agent")}} 用来驱动脚本执行环境的事件循环之前,该微任务才会被执行。</span> 事件循环既可能是浏览器的主事件循环也可能是被一个 <a href="/en-US/docs/Web/API/Web_Workers_API">web worker</a> 所驱动的事件循环。这使得给定的函数在没有其他脚本执行干扰的情况下运行,也保证了微任务能在用户代理有机会对该微任务带来的行为做出反应之前运行。</p>
+
+<p>JavaScript 中的 <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">promises</a> 和 <a href="//en-US/docs/Web/API/Mutation_Observer_API">Mutation Observer API</a> 都使用微任务队列去运行它们的回调函数,但当能够推迟工作直到当前事件循环过程完结时,也是可以执行微任务的时机。为了允许第三方库、框架、polyfills 能使用微任务,{{domxref("Window")}} 暴露了 {{domxref("WindowOrWorkerGlobalScope.queueMicrotask", "queueMicrotask()")}} 方法,而 {{domxref("Worker")}} 接口则通过{{domxref("WindowOrWorkerGlobalScope")}} mixin 提供了同名的 queueMicrotask() 方法。</p>
+
+<h2 id="任务_vs_微任务">任务 vs 微任务</h2>
+
+<p>为了正确地讨论微任务,首先最好知道什么是一个 JavaScript 任务以及微任务如何区别于任务。这里是一个快速、简单的解释,但若你想了解更多细节,可以阅读这篇文章中的信息 <a href="/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide/In_depth">In depth: Microtasks and the JavaScript runtime environment</a>.</p>
+
+<h3 id="任务(Tasks)">任务(Tasks)</h3>
+
+<p>一个 <strong>任务</strong> 就是由执行诸如从头执行一段程序、执行一个事件回调或一个 interval/timeout 被触发之类的标准机制而被调度的任意 JavaScript 代码。这些都在 <strong>任务队列(task queue)</strong>上被调度。</p>
+
+<p>在以下时机,任务会被添加到任务队列:</p>
+
+<ul>
+ <li>一段新程序或子程序被直接执行时(比如从一个控制台,或在一个 {{HTMLElement("script")}} 元素中运行代码)。</li>
+ <li>触发了一个事件,将其回调函数添加到任务队列时。</li>
+ <li>执行到一个由 {{domxref("WindowOrWorkerGlobalScope.setTimeout", "setTimeout()")}} 或 {{domxref("WindowOrWorkerGlobalScope.setInterval", "setInterval()")}} 创建的 timeout 或 interval,以致相应的回调函数被添加到任务队列时。</li>
+</ul>
+
+<p>事件循环驱动你的代码按照这些任务排队的顺序,一个接一个地处理它们。在当前迭代轮次中,只有那些当事件循环过程开始时 <em>已经处于任务队列中</em> 的任务会被执行。其余的任务不得不等待到下一次迭代。</p>
+
+<h3 id="微任务(Microtasks)">微任务(Microtasks)</h3>
+
+<p>起初微任务和任务之间的差异看起来不大。它们很相似;都由位于某个队列的 JavaScript 代码组成并在合适的时候运行。但是,只有在迭代开始时队列中存在的任务才会被事件循环一个接一个地运行,这和处理微任务队列是殊为不同的。</p>
+
+<p>有两点关键的区别。</p>
+
+<p>首先,每当一个任务存在,事件循环都会检查该任务是否正把控制权交给其他 JavaScript 代码。如若不然,事件循环就会运行微任务队列中的所有微任务。接下来微任务循环会在事件循环的每次迭代中被处理多次,包括处理完事件和其他回调之后。</p>
+
+<p>其次,如果一个微任务通过调用  {{domxref("WindowOrWorkerGlobalScope.queueMicrotask", "queueMicrotask()")}}, 向队列中加入了更多的微任务,则那些新加入的微任务 <em>会早于下一个任务运行</em> 。这是因为事件循环会持续调用微任务直至队列中没有留存的,即使是在有更多微任务持续被加入的情况下。</p>
+
+<div class="blockIndicator warning">
+<p><strong>注意:</strong> 因为微任务自身可以入列更多的微任务,且事件循环会持续处理微任务直至队列为空,那么就存在一种使得事件循环无尽处理微任务的真实风险。如何处理递归增加微任务是要谨慎而行的。</p>
+</div>
+
+<h2 id="使用微任务">使用微任务</h2>
+
+<p>在谈论更多之前,再次注意到一点是重要的,那就是如果可能的话,大部分开发者并不应该过多的使用微任务。在基于现代浏览器的 JavaScript 开发中有一个高度专业化的特性,那就是允许你调度代码跳转到其他事情之前,而那些事情原本是处于用户计算机中一大堆等待发生的事情集合之中的。滥用这种能力将带来性能问题。</p>
+
+<h3 id="入列微任务">入列微任务</h3>
+
+<p>就其本身而言,应该使用微任务的典型情况,要么只有在没有其他办法的时候,要么是当创建框架或库时需要使用微任务达成其功能。虽然在过去要使得入列微任务成为可能有可用的技巧(比如创建一个立即 resolve 的 promise),但新加入的 {{domxref("WindowOrWorkerGlobalScope.queueMicrotask", "queueMicrotask()")}} 方法增加了一种标准的方式,可以安全的引入微任务而避免使用额外的技巧。</p>
+
+<p>通过引入 <code>queueMicrotask()</code>,由晦涩地使用 promise 去创建微任务而带来的风险就可以被避免了。举例来说,当使用 promise 创建微任务时,由回调抛出的异常被报告为 rejected promises 而不是标准异常。同时,创建和销毁 promise 带来了事件和内存方面的额外开销,这是正确入列微任务的函数应该避免的。</p>
+
+<p>简单的传入一个 JavaScript {{jsxref("Function")}} ,以在 <code>queueMicrotask()</code> 方法中处理微任务时供其上下文调用即可;取决于当前执行上下文, <code>queueMicrotask()</code> 以定义的形式被暴露在 {{domxref("Window")}} 或 {{domxref("Worker")}} 接口上。</p>
+
+<pre class="brush: js">queueMicrotask(() =&gt; {
+ /* 微任务中将运行的代码 */
+});
+</pre>
+
+<p>微任务函数本身没有参数,也不返回值。</p>
+
+<h3 id="何时使用微任务">何时使用微任务</h3>
+
+<p>在本章节中,我们来看看微任务特别有用的场景。通常,这些场景关乎捕捉或检查结果、执行清理等;其时机晚于一段 JavaScript 执行上下文主体的退出,但早于任何事件处理函数、timeouts 或 intervals 及其他回调被执行。</p>
+
+<p>何时是那种有用的时候?</p>
+
+<p>使用微任务的最主要原因简单归纳为:确保任务顺序的一致性,即便当结果或数据是同步可用的,也要同时减少操作中用户可感知到的延迟而带来的风险。</p>
+
+<h4 id="保证条件性使用_promises_时的顺序">保证条件性使用 promises 时的顺序</h4>
+
+<p>微任务可被用来确保执行顺序总是一致的一种情形,是当 promise 被用在一个 <code>if...else</code> 语句(或其他条件性语句)中、但并不在其他子句中的时候。考虑如下代码:</p>
+
+<pre class="brush: js">customElement.prototype.getData = url =&gt; {
+ if (this.cache[url]) {
+ this.data = this.cache[url];
+ this.dispatchEvent(new Event("load"));
+ } else {
+ fetch(url).then(result =&gt; result.arrayBuffer()).then(data =&gt; {
+ this.cache[url] = data;
+ this.data = data;
+ this.dispatchEvent(new Event("load"));
+ )};
+ }
+};</pre>
+
+<p>这段代码带来的问题是,通过在 <code>if...else</code> 语句的其中一个分支(此例中为缓存中的图片地址可用时)中使用一个任务而 promise 包含在 <code>else</code> 子句中,我们面临了操作顺序可能不同的局势;比方说,像下面看起来的这样:</p>
+
+<pre class="brush: js">element.addEventListener("load", () =&gt; console.log("Loaded data"));
+console.log("Fetching data...");
+element.getData();
+console.log("Data fetched");
+</pre>
+
+<p>连续执行两次这段代码会形成下表中的结果:</p>
+
+<table class="standard-table">
+ <caption>数据未缓存的结果(左) vs. 缓存中有数据的结果</caption>
+ <thead>
+ <tr>
+ <th scope="col">数据未缓存</th>
+ <th scope="col">数据已缓存</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <pre>
+Fetching data
+Data fetched
+Loaded data
+</pre>
+ </td>
+ <td>
+ <pre>
+Fetching data
+Loaded data
+Data fetched
+</pre>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<p>甚至更糟的是,有时元素的 <code>data</code></p>
+
+<p>属性会被设置,还有时当这段代码结束运行时却不会被设置。</p>
+
+<p>我们可以通过在 <code>if</code> 子句里使用一个微任务来确保操作顺序的一致性,以达到平衡两个子句的目的:</p>
+
+<pre class="brush: js">customElement.prototype.getData = url =&gt; {
+ if (this.cache[url]) {
+ queueMicrotask(() =&gt; {
+ this.data = this.cache[url];
+ this.dispatchEvent(new Event("load"));
+ });
+ } else {
+ fetch(url).then(result =&gt; result.arrayBuffer()).then(data =&gt; {
+ this.cache[url] = data;
+ this.data = data;
+ this.dispatchEvent(new Event("load"));
+ )};
+ }
+};</pre>
+
+<p>通过在两种情况下各自都通过一个微任务(<code>if</code> 中用的是 <code>queueMicrotask()</code> 而 <code>else</code> 子句中通过 {{domxref("WindowOrWorkerGlobalScope.fetch", "fetch()")}} 使用了 promise)处理了设置 <code>data</code> 和触发 <code>load</code> 事件,平衡了两个子句。</p>
+
+<h4 id="批量操作">批量操作</h4>
+
+<p>也可以使用微任务从不同来源将多个请求收集到单一的批处理中,从而避免对处理同类工作的多次调用可能造成的开销。</p>
+
+<p>下面的代码片段创建了一个函数,将多个消息放入一个数组中批处理,通过一个微任务在上下文退出时将这些消息作为单一的对象发送出去。</p>
+
+<pre class="brush: js">const messageQueue = [];
+
+let sendMessage = message =&gt; {
+ messageQueue.push(message);
+
+ if (messageQueue.length === 1) {
+ queueMicrotask(() =&gt; {
+ const json = JSON.stringify(messageQueue);
+ messageQueue.length = 0;
+ fetch("url-of-receiver", json);
+ });
+ }
+};
+</pre>
+
+<p>当 <code>sendMessage()</code></p>
+
+<p>被调用时,指定的消息首先被推入消息队列数组。接着事情就变得有趣了。</p>
+
+<p>如果我们刚加入数组的消息是第一条,就入列一个将会发送一个批处理的微任务。照旧,当 JavaScript 执行路径到达顶层,恰在运行回调之前,那个微任务将会执行。这意味着之后的间歇期内造成的对 <code>sendMessage()</code> 的任何调用都会将其各自的消息推入消息队列,但囿于入列微任务逻辑之前的数组长度检查,不会有新的微任务入列。</p>
+
+<p>当微任务运行之时,等待它处理的可能是一个有若干条消息的数组。微任务函数先是通过 {{jsxref("JSON.stringify()")}} 方法将消息数组编码为 JSON。其后,数组中的内容就不再需要了,所以清空 <code>messageQueue</code> 数组。最后,使用 {{domxref("WindowOrWorkerGlobalScope.fetch", "fetch()")}} 方法将编码后的 JSON 发往服务器。</p>
+
+<p>这使得同一次事件循环迭代期间发生的每次 <code>sendMessage()</code> 调用将其消息添加到同一个 <code>fetch()</code> 操作中,而不会让诸如 timeouts 等其他可能的定时任务推迟传递。</p>
+
+<p>服务器将接到 JSON 字符串,然后大概会将其解码并处理其从结果数组中找到的消息。</p>
+
+<h2 id="例子">例子</h2>
+
+<h3 id="简单微任务示例">简单微任务示例</h3>
+
+<p>在这个简单的例子中,我们将看到入列一个微任务后,会引起其回调函数在顶层脚本完毕后运行。</p>
+
+<div class="hidden">
+<h4 id="HTML">HTML</h4>
+
+<pre class="brush: html">&lt;pre id="log"&gt;
+&lt;/pre&gt;</pre>
+</div>
+
+<h4 id="JavaScript">JavaScript</h4>
+
+<div class="hidden">
+<p>以下代码用于记录输出。</p>
+
+<pre class="brush: js">let logElem = document.getElementById("log");
+let log = s =&gt; logElem.innerHTML += s + "&lt;br&gt;";</pre>
+</div>
+
+<p>在下面的代码中,我们看到对 {{domxref("WindowOrWorkerGlobalScope.queueMicrotask", "queueMicrotask()")}} 的一次调用被用来调度一个微任务以使其运行。这次调用包含了 <code>log()</code>,一个简单的向屏幕输出文字的自定义函数。</p>
+
+<pre class="brush: js">log("Before enqueueing the microtask");
+queueMicrotask(() =&gt; {
+ log("The microtask has run.")
+});
+log("After enqueueing the microtask");</pre>
+
+<h4 id="结果">结果</h4>
+
+<p>{{EmbedLiveSample("简单微任务示例", 640, 80)}}</p>
+
+<h3 id="timeout_和微任务的示例">timeout 和微任务的示例</h3>
+
+<p>在这个例子中,一个 timeout 在 0 毫秒后被触发(或者 "尽可能快")。这演示了当调用一个新任务(如通过使用  <code>setTimeout()</code>)时的“尽可能快”意味着什么,以及比之于使用一个微任务的不同。</p>
+
+<div class="hidden">
+<h4 id="HTML_2">HTML</h4>
+
+<pre class="brush: html">&lt;pre id="log"&gt;
+&lt;/pre&gt;</pre>
+</div>
+
+<h4 id="JavaScript_2">JavaScript</h4>
+
+<div class="hidden">
+<p>以下代码用于记录输出。</p>
+
+<pre class="brush: js">let logElem = document.getElementById("log");
+let log = s =&gt; logElem.innerHTML += s + "&lt;br&gt;";</pre>
+</div>
+
+<p>在下面的代码中,我们看到对 {{domxref("WindowOrWorkerGlobalScope.queueMicrotask", "queueMicrotask()")}} 的一次调用被用来调度一个微任务以使其运行。这次调用包含了 <code>log()</code>,一个简单的向屏幕输出文字的自定义函数。</p>
+
+<p>以下代码调度了一个 0 毫秒后触发的 timeout,而后入列了一个微任务。前后被对 <code>log()</code> 的调用包住,输出附加的信息。</p>
+
+<pre class="brush: js">let callback = () =&gt; log("Regular timeout callback has run");
+
+let urgentCallback = () =&gt; log("*** Oh noes! An urgent callback has run!");
+
+log("Main program started");
+setTimeout(callback, 0);
+queueMicrotask(urgentCallback);
+log("Main program exiting");</pre>
+
+<h4 id="结果_2">结果</h4>
+
+<p>{{EmbedLiveSample("timeout_和微任务的示例", 640, 100)}}</p>
+
+<p>可以注意到,从主程序体中输出的日志首先出现,接下来是微任务中的输出,其后是 timeout 的回调。这是因为当处理主程序运行的任务退出后,微任务队列先于 timeout 回调所在的任务队列被处理。要记住任务和微任务是保持各自独立的队列的,且微任务先执行有助于保持这一点。</p>
+
+<h3 id="来自函数的微任务">来自函数的微任务</h3>
+
+<p>这个例子通过增加一个完成同样工作的函数,略微地扩展了前一个例子。该函数使用 <code>queueMicrotask()</code> 调度一个微任务。此例的重要之处是微任务不在其所处的函数退出时,而是在主程序退出时被执行。</p>
+
+<div class="hidden">
+<h4 id="HTML_3">HTML</h4>
+
+<pre class="brush: html">&lt;pre id="log"&gt;
+&lt;/pre&gt;</pre>
+</div>
+
+<h4 id="JavaScript_3">JavaScript</h4>
+
+<div class="hidden">
+<p>以下代码用于记录输出。</p>
+
+<pre class="brush: js">let logElem = document.getElementById("log");
+let log = s =&gt; logElem.innerHTML += s + "&lt;br&gt;";</pre>
+</div>
+
+<p>以下是主程序代码。这里的 <code>doWork()</code> 函数调用了 <code>queueMicrotask()</code>,但微任务仍在整个程序退出时才触发,因为那才是任务退出而执行栈上为空的时刻。</p>
+
+<pre class="brush: js">let callback = () =&gt; log("Regular timeout callback has run");
+
+let urgentCallback = () =&gt; log("*** Oh noes! An urgent callback has run!");
+
+let doWork = () =&gt; {
+ let result = 1;
+
+ queueMicrotask(urgentCallback);
+
+ for (let i=2; i&lt;=10; i++) {
+ result *= i;
+ }
+ return result;
+};
+
+log("Main program started");
+setTimeout(callback, 0);
+log(`10! equals ${doWork()}`);
+log("Main program exiting");</pre>
+
+<h4 id="结果_3">结果</h4>
+
+<p>{{EmbedLiveSample("来自函数的微任务", 640, 100)}}</p>
+
+<h2 id="See_also">See also</h2>
+
+<ul>
+ <li><a href="/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide/In_depth">In depth: Microtasks and the JavaScript runtime environment</a></li>
+ <li>{{domxref("WindowOrWorkerGlobalScope.queueMicrotask", "queueMicrotask()")}}</li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous">Asynchronous JavaScript</a>
+ <ul>
+ <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts">General asynchronous programming concepts</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">Introducing asynchronous JavaScript</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">Cooperative asynchronous JavaScript: Timeouts and intervals</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">Graceful asynchronous programming with Promises</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">Choosing the right approach</a></li>
+ </ul>
+ </li>
+</ul>