From d53bae971e455859229bcb3246e29bcbc85179d2 Mon Sep 17 00:00:00 2001 From: MDN Date: Tue, 8 Mar 2022 00:12:08 +0000 Subject: [CRON] sync translated content --- .../javascript/asynchronous/concepts/index.html | 163 --------------------- 1 file changed, 163 deletions(-) delete mode 100644 files/zh-cn/learn/javascript/asynchronous/concepts/index.html (limited to 'files/zh-cn/learn/javascript/asynchronous/concepts/index.html') diff --git a/files/zh-cn/learn/javascript/asynchronous/concepts/index.html b/files/zh-cn/learn/javascript/asynchronous/concepts/index.html deleted file mode 100644 index 757eee777c..0000000000 --- a/files/zh-cn/learn/javascript/asynchronous/concepts/index.html +++ /dev/null @@ -1,163 +0,0 @@ ---- -title: 通用异步编程概念 -slug: Learn/JavaScript/Asynchronous/Concepts -tags: - - JavaScript - - Promises - - Threads - - 学习 - - 异步 - - 阻塞 -translation_of: Learn/JavaScript/Asynchronous/Concepts -original_slug: learn/JavaScript/异步/概念 ---- -
{{LearnSidebar}}{{NextMenu("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous")}}
- -

在本文中,我们将介绍与异步编程相关的一些重要概念,以及它们在浏览器和JavaScript里的体现。在学习本系列的其他文章之前,你应该先理解这些概念。

- - - - - - - - - - - - -
预备条件:拥有基本的计算机知识,对JavaScript原理有一定了解。
目标:理解异步编程的基本概念,以及异步编程在浏览器和JavaScript里面的表现。
- -

异步?

- -

通常来说,程序都是顺序执行,同一时刻只会发生一件事。如果一个函数依赖于另一个函数的结果,它只能等待那个函数结束才能继续执行,从用户的角度来说,整个程序才算运行完毕.

- -

Mac 用户有时会经历过这种旋转的彩虹光标(常称为沙滩球),操作系统通过这个光标告诉用户:“现在运行的程序正在等待其他的某一件事情完成,才能继续运行,都这么长的时间了,你一定在担心到底发生了什么事情”。

- -

multi-colored macos beachball busy spinner

- -

这是令人沮丧的体验,没有充分利用计算机的计算能力 — 尤其是在计算机普遍都有多核CPU的时代,坐在那里等待毫无意义,你完全可以在另一个处理器内核上干其他的工作,同时计算机完成耗时任务的时候通知你。这样你可以同时完成其他工作,这就是异步编程的出发点。你正在使用的编程环境(就web开发而言,编程环境就是web浏览器)负责为你提供异步运行此类任务的API。

- -

产生阻塞的代码

- -

异步技术非常有用,特别是在web编程。当浏览器里面的一个web应用进行密集运算还没有把控制权返回给浏览器的时候,整个浏览器就像冻僵了一样,这叫做阻塞;这时候浏览器无法继续处理用户的输入并执行其他任务,直到web应用交回处理器的控制。

- -

我们来看一些阻塞的例子。

- -

例子: simple-sync.html  (see it running live), 在按钮上添加了一个事件监听器,当按钮被点击,它就开始运行一个非常耗时的任务(计算1千万个日期,并在console里显示最后一个日期),然后在DOM里面添加一个段落:

- -
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);
-});
- -

运行这个例子的时候,打开JavaScript console,然后点击按钮 — 你会注意到,直到日期的运算结束,最后一个日期在console上显示出来,段落才会出现在网页上。代码按照源代码的顺序执行,只有前面的代码结束运行,后面的代码才会执行。

- -
-

Note: 这个例子不现实:在实际情况中一般不会发生,没有谁会计算1千万次日期,它仅仅提供一个非常直观的体验.

-
- -

第二个例子, simple-sync-ui-blocking.html (see it live), 我们模拟一个在现实的网页可能遇到的情况:因为渲染UI而阻塞用户的互动,这个例子有2个按钮:

- - - -
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!')
-);
- -

如果你点击第一个按钮,然后快速点击第二个,会注意到alert消息并没有出现,只有等到圆圈都画完以后,才会出现:因为第一个操作没有完成之前阻塞了第二个操作的运行.

- -
-

Note: 当然,这个例子也很丑陋,因为我们只是在模拟阻塞效果。但在现实中,这是一个很常见的问题。开发人员们一直在努力缓解它。

-
- -

为什么是这样? 答案是:JavaScript一般来说是单线程的(single threaded接着我们来介绍线程的概念。

- -

线程

- -

一个线程是一个基本的处理过程,程序用它来完成任务。每个线程一次只能执行一个任务:

- -
Task A --> Task B --> Task C
- -

每个任务顺序执行,只有前面的结束了,后面的才能开始。

- -

正如我们之前所说,现在的计算机大都有多个内核(core),因此可以同时执行多个任务。支持多线程的编程语言可以使用计算机的多个内核,同时完成多个任务:

- -
Thread 1: Task A --> Task B
-Thread 2: Task C --> Task D
- -

JavaScript 是单线程的

- -

JavaScript 传统上是单线程的。即使有多个内核,也只能在单一线程上运行多个任务,此线程称为主线程(main thread)。我们上面的例子运行如下:

- -
Main thread: Render circles to canvas --> Display alert()
- -

经过一段时间,JavaScript获得了一些工具来帮助解决这种问题。通过 Web workers 可以把一些任务交给一个名为worker的单独的线程,这样就可以同时运行多个JavaScript代码块。一般来说,用一个worker来运行一个耗时的任务,主线程就可以处理用户的交互(避免了阻塞)

- -
Main thread: Task A --> Task C
-Worker thread: Expensive task B
- -

记住这些,请查看simple-sync-worker.html (see it running live) , 再次打开浏览器的JavaScript 控制台。这个例子重写了前例:在一个单独的worker线程中计算一千万次日期,你再点击按钮,现在浏览器可以在日期计算完成之前显示段落,阻塞消失了。

- -

异步代码

- -

web workers相当有用,但是他们确实也有局限。主要的一个问题是他们不能访问 {{Glossary("DOM")}} — 不能让一个worker直接更新UI。我们不能在worker里面渲染1百万个蓝色圆圈,它基本上只能做算数的苦活。

- -

其次,虽然在worker里面运行的代码不会产生阻塞,但是基本上还是同步的。当一个函数依赖于几个在它之前运行的过程的结果,这就会成为问题。考虑下面的情况:

- -
Main thread: Task A --> Task B
- -

在这种情况下,比如说Task A 正在从服务器上获取一个图片之类的资源,Task B 准备在图片上加一个滤镜。如果开始运行Task A 后立即尝试运行Task B,你将会得到一个错误,因为图像还没有获取到。

- -
Main thread: Task A --> Task B --> |Task D|
-Worker thread: Task C -----------> |      |
- -

在这种情况下,假设Task D 要同时使用 Task B 和Task C的结果,如果我们能保证这两个结果同时提供,程序可能正常运行,但是这不太可能。如果Task D 尝试在其中一个结果尚未可用的情况下就运行,程序就会抛出一个错误。

- -

为了解决这些问题,浏览器允许我们异步运行某些操作。像Promises 这样的功能就允许让一些操作运行 (比如:从服务器上获取图片),然后等待直到结果返回,再运行其他的操作:

- -
Main thread: Task A                   Task B
-    Promise:      |__async operation__|
- -

由于操作发生在其他地方,因此在处理异步操作的时候,主线程不会被阻塞。

- -

我们将在下一篇文章中开始研究如何编写异步代码。 非常令人兴奋,对吧? 继续阅读!

- -

总结

- -

围绕异步编程领域,现代软件设计正在加速旋转,就为了让程序在一个时间内做更多的事情。当你使用更新更强大的API时,你会发现在更多的情况下,使用异步编程是唯一的途径。以前写异步代码很困难,现在也需要你来适应,但是已经变容易了很多。在余下的部分,我们将进一步探讨异步代码的重要性,以及如何设计代码来防止前面已经提到过的问题。

- -

模块大纲

- - -- cgit v1.2.3-54-g00ecf