diff options
author | Florian Merz <me@fiji-flo.de> | 2021-02-11 12:56:40 +0100 |
---|---|---|
committer | Florian Merz <me@fiji-flo.de> | 2021-02-11 12:56:40 +0100 |
commit | 310fd066e91f454b990372ffa30e803cc8120975 (patch) | |
tree | d5d900deb656a5da18e0b60d00f0db73f3a2e88e /files/zh-cn/learn/javascript/asynchronous/concepts/index.html | |
parent | 8260a606c143e6b55a467edf017a56bdcd6cba7e (diff) | |
download | translated-content-310fd066e91f454b990372ffa30e803cc8120975.tar.gz translated-content-310fd066e91f454b990372ffa30e803cc8120975.tar.bz2 translated-content-310fd066e91f454b990372ffa30e803cc8120975.zip |
unslug zh-cn: move
Diffstat (limited to 'files/zh-cn/learn/javascript/asynchronous/concepts/index.html')
-rw-r--r-- | files/zh-cn/learn/javascript/asynchronous/concepts/index.html | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/files/zh-cn/learn/javascript/asynchronous/concepts/index.html b/files/zh-cn/learn/javascript/asynchronous/concepts/index.html new file mode 100644 index 0000000000..6e59cda54b --- /dev/null +++ b/files/zh-cn/learn/javascript/asynchronous/concepts/index.html @@ -0,0 +1,162 @@ +--- +title: 通用异步编程概念 +slug: learn/JavaScript/异步/概念 +tags: + - JavaScript + - Promises + - Threads + - 学习 + - 异步 + - 阻塞 +translation_of: Learn/JavaScript/Asynchronous/Concepts +--- +<div>{{LearnSidebar}}{{NextMenu("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous")}}</div> + +<p>在本文中,我们将介绍与异步编程相关的一些重要概念,以及它们在浏览器和JavaScript里的体现。在学习本系列的其他文章之前,你应该先理解这些概念。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备条件:</th> + <td>拥有基本的计算机知识,对JavaScript原理有一定了解。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解异步编程的基本概念,以及异步编程在浏览器和JavaScript里面的表现。</td> + </tr> + </tbody> +</table> + +<h2 id="异步">异步?</h2> + +<p>通常来说,程序都是顺序执行,同一时刻只会发生一件事。如果一个函数依赖于另一个函数的结果,它只能等待那个函数结束才能继续执行,从用户的角度来说,整个程序才算运行完毕.</p> + +<p>Mac 用户有时会经历过这种旋转的彩虹光标(常称为沙滩球),操作系统通过这个光标告诉用户:“现在运行的程序正在等待其他的某一件事情完成,才能继续运行,都这么长的时间了,你一定在担心到底发生了什么事情”。</p> + +<p><img alt="multi-colored macos beachball busy spinner" src="https://mdn.mozillademos.org/files/16577/beachball.jpg" style="display: block; margin: 0 auto;"></p> + +<p>这是令人沮丧的体验,没有充分利用计算机的计算能力 — 尤其是在计算机普遍都有多核CPU的时代,坐在那里等待毫无意义,你完全可以在另一个处理器内核上干其他的工作,同时计算机完成耗时任务的时候通知你。这样你可以同时完成其他工作,这就是<strong>异步编程</strong>的出发点。你正在使用的编程环境(就web开发而言,编程环境就是web浏览器)负责为你提供异步运行此类任务的API。</p> + +<h2 id="产生阻塞的代码">产生阻塞的代码</h2> + +<p>异步技术非常有用,特别是在web编程。当浏览器里面的一个web应用进行密集运算还没有把控制权返回给浏览器的时候,整个浏览器就像冻僵了一样,这叫做<strong>阻塞;</strong>这时候浏览器无法继续处理用户的输入并执行其他任务,直到web应用交回处理器的控制。</p> + +<p>我们来看一些<strong>阻塞</strong>的例子。</p> + +<p>例子: <a href="https://github.com/mdn/learning-area/tree/master/javascript/asynchronous/introducing">simple-sync.html</a> (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/simple-sync.html">see it running live</a>), 在按钮上添加了一个事件监听器,当按钮被点击,它就开始运行一个非常耗时的任务(计算1千万个日期,并在console里显示最后一个日期),然后在DOM里面添加一个段落:</p> + +<pre class="brush: js">const btn = document.querySelector('button'); +btn.addEventListener('click', () => { + let myDate; + for(let i = 0; i < 10000000; i++) { + let date = new Date(); + myDate = date + } + + console.log(myDate); + + let pElem = document.createElement('p'); + pElem.textContent = 'This is a newly-added paragraph.'; + document.body.appendChild(pElem); +});</pre> + +<p>运行这个例子的时候,打开JavaScript console,然后点击按钮 — 你会注意到,直到日期的运算结束,最后一个日期在console上显示出来,段落才会出现在网页上。代码按照源代码的顺序执行,只有前面的代码结束运行,后面的代码才会执行。</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 这个例子不现实:在实际情况中一般不会发生,没有谁会计算1千万次日期,它仅仅提供一个非常直观的体验.</p> +</div> + +<p>第二个例子, <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/simple-sync-ui-blocking.html">simple-sync-ui-blocking.html</a> (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/simple-sync-ui-blocking.html">see it live</a>), 我们模拟一个在现实的网页可能遇到的情况:因为渲染UI而阻塞用户的互动,这个例子有2个按钮:</p> + +<ul> + <li>"Fill canvas" : 点击的时候用1百万个蓝色的圆填满整个{{htmlelement("canvas")}} .</li> + <li>"Click me for alert" :点击显示alert 消息.</li> +</ul> + +<pre class="brush: js">function expensiveOperation() { + for(let i = 0; i < 1000000; i++) { + ctx.fillStyle = 'rgba(0,0,255, 0.2)'; + ctx.beginPath(); + ctx.arc(random(0, canvas.width), random(0, canvas.height), 10, degToRad(0), degToRad(360), false); + ctx.fill() + } +} + +fillBtn.addEventListener('click', expensiveOperation); + +alertBtn.addEventListener('click', () => + alert('You clicked me!') +);</pre> + +<p>如果你点击第一个按钮,然后快速点击第二个,会注意到alert消息并没有出现,只有等到圆圈都画完以后,才会出现:因为第一个操作没有完成之前阻塞了第二个操作的运行.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 当然,这个例子也很丑陋,因为我们只是在模拟阻塞效果。但在现实中,这是一个很常见的问题。开发人员们一直在努力缓解它。</p> +</div> + +<p>为什么是这样? 答案是:JavaScript一般来说是单线程的(<strong>single threaded</strong>)<strong>。</strong>接着我们来介绍<strong>线程</strong>的概念。</p> + +<h2 id="线程">线程</h2> + +<p>一个<strong>线程</strong>是一个基本的处理过程,程序用它来完成任务。每个线程一次只能执行一个任务:</p> + +<pre>Task A --> Task B --> Task C</pre> + +<p>每个任务顺序执行,只有前面的结束了,后面的才能开始。</p> + +<p>正如我们之前所说,现在的计算机大都有多个内核(core),因此可以同时执行多个任务。支持多线程的编程语言可以使用计算机的多个内核,同时完成多个任务:</p> + +<pre>Thread 1: Task A --> Task B +Thread 2: Task C --> Task D</pre> + +<h3 id="JavaScript_是单线程的">JavaScript 是单线程的</h3> + +<p>JavaScript 传统上是单线程的。即使有多个内核,也只能在单一线程上运行多个任务,此线程称为主线程(<strong>main thread</strong>)。我们上面的例子运行如下:</p> + +<pre>Main thread: Render circles to canvas --> Display alert()</pre> + +<p>经过一段时间,JavaScript获得了一些工具来帮助解决这种问题。通过 <a href="/en-US/docs/Web/API/Web_Workers_API">Web workers</a> 可以把一些任务交给一个名为worker的单独的线程,这样就可以同时运行多个JavaScript代码块。一般来说,用一个worker来运行一个耗时的任务,主线程就可以处理用户的交互(避免了阻塞)</p> + +<pre>Main thread: Task A --> Task C +Worker thread: Expensive task B</pre> + +<p>记住这些,请查看<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/simple-sync-worker.html">simple-sync-worker.html</a> (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/simple-sync-worker.html">see it running live</a>) , 再次打开浏览器的JavaScript 控制台。这个例子重写了前例:在一个单独的worker线程中计算一千万次日期,你再点击按钮,现在浏览器可以在日期计算完成之前显示段落,阻塞消失了。</p> + +<h2 id="异步代码">异步代码</h2> + +<p>web workers相当有用,但是他们确实也有局限。主要的一个问题是他们不能访问 {{Glossary("DOM")}} — 不能让一个worker直接更新UI。我们不能在worker里面渲染1百万个蓝色圆圈,它基本上只能做算数的苦活。</p> + +<p>其次,虽然在worker里面运行的代码不会产生阻塞,但是基本上还是同步的。当一个函数依赖于几个在它之前运行的过程的结果,这就会成为问题。考虑下面的情况:</p> + +<pre>Main thread: Task A --> Task B</pre> + +<p>在这种情况下,比如说Task A 正在从服务器上获取一个图片之类的资源,Task B 准备在图片上加一个滤镜。如果开始运行Task A 后立即尝试运行Task B,你将会得到一个错误,因为图像还没有获取到。</p> + +<pre>Main thread: Task A --> Task B --> |Task D| +Worker thread: Task C -----------> | |</pre> + +<p>在这种情况下,假设Task D 要同时使用 Task B 和Task C的结果,如果我们能保证这两个结果同时提供,程序可能正常运行,但是这不太可能。如果Task D 尝试在其中一个结果尚未可用的情况下就运行,程序就会抛出一个错误。</p> + +<p>为了解决这些问题,浏览器允许我们异步运行某些操作。像<a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a> 这样的功能就允许让一些操作运行 (比如:从服务器上获取图片),然后等待直到结果返回,再运行其他的操作:</p> + +<pre>Main thread: Task A Task B + Promise: |__async operation__|</pre> + +<p>由于操作发生在其他地方,因此在处理异步操作的时候,主线程不会被阻塞。</p> + +<p><span class="tlid-translation translation" lang="zh-CN"><span title="">我们将在下一篇文章中开始研究如何编写异步代码。</span> <span title="">非常令人兴奋,对吧?</span> <span title="">继续阅读!</span></span></p> + +<h2 id="总结">总结</h2> + +<p>围绕异步编程领域,现代软件设计正在加速旋转,就为了让程序在一个时间内做更多的事情。当你使用更新更强大的API时,你会发现在更多的情况下,使用异步编程是唯一的途径。以前写异步代码很困难,现在也需要你来适应,但是已经变容易了很多。在余下的部分,我们将进一步探讨异步代码的重要性,以及如何设计代码来防止前面已经提到过的问题。</p> + +<h2 id="模块大纲">模块大纲</h2> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts">通用异步编程概念</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">介绍异步JavaScript</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">合作异步JavaScript: Timeouts and intervals</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">使用Promises优雅的异步编程</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Async_await">async and await:让异步编程更容易</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">选择正确的方法</a></li> +</ul> |