1
0
mirror of https://github.com/apachecn/eloquent-js-3e-zh.git synced 2025-05-27 22:52:20 +00:00
This commit is contained in:
wizardforcel 2018-06-02 15:12:20 +08:00
parent 97fba152c2
commit 8ade687fea
3 changed files with 66 additions and 66 deletions

132
21.md
View File

@ -4,49 +4,51 @@
>
> Margaret Fuller
技能分享会议将拥有相同兴趣的聚集到一起并针对了解的知识进行简短且非正式的展示。在园艺的技能分享会议上可以解释如何耕作芹菜。如果在编程的技能分享小组中你可以顺便给每个人讲讲Node.js。
![](img/21-0.jpg)
在计算机专业中这类聚会往往名为用户小组是开阔眼界、了解行业新动态或仅仅接触兴趣相同的人的好方法。许多大城市都会有JavaScript聚会。这类聚会往往是可以免费参加的而且我发现我参加过的那些聚会都非常友好热情
技能分享会是一个活动,其中兴趣相同的人聚在一起,针对他们所知的事情进行小型非正式的展示。在园艺技能分享会上,可以解释如何耕作芹菜。如果在编程技能分享小组中,你可以顺便给每个人讲讲 Node.js
最后的项目章节中,我们的目标是建立网站,管理特定技能分享会议的讨论内容。假设一个小组的人会在成员办公室中定期举办关于独轮车的会议。上一个组织者搬到了另一个城市,并且没人可以站出来接下来他的任务。我们需要一个系统,让参与者可以在系统中发言并相互讨论,这样就不需要一个中心组织人员了
计算机领域中,这类聚会往往名为用户小组,是开阔眼界、了解行业新动态或仅仅接触兴趣相同的人的好方法。许多大城市都会有 JavaScript 聚会。这类聚会往往是可以免费参加的,而且我发现我参加过的那些聚会都非常友好热情
就像上一章一样,本章中的一些代码是为 Node.js 编写的,并且直接在您正在查看的 HTML页面中运行它不太可行。 该项目的完整代码可以从[`eloquentjavascript.net/code/skillsharing.zip`](https://eloquentjavascript.net/code/skillsharing.zip)下载
在最后的项目章节中,我们的目标是建立网站,管理特定技能分享会的讨论内容。假设一个小组的人会在成员办公室中定期举办关于独轮车的聚会。上一个组织者搬到了另一个城市,并且没人可以站出来接下来他的任务。我们需要一个系统,让参与者可以在系统中发言并相互讨论,这样就不需要一个中心组织人员了
### 21.1 设计
就像上一章一样,本章中的一些代码是为 Node.js 编写的,并且直接在你正在查看的 HTML页面中运行它不太可行。 该项目的完整代码可以从[`eloquentjavascript.net/code/skillsharing.zip`](https://eloquentjavascript.net/code/skillsharing.zip)下载。
本项目的服务器部分为Node.js编写客户端部分则为浏览器编写。服务器存储系统数据并将其提供给客户端。它也提供实现客户端系统的文件。
## 设计
服务器保存了为下次会议提出的对话列表。每个对话包括参与人员姓名、标题和该对话的相关评论。客户端允许用户提出新的对话将对话添加到列表中、删除对话和评论已存在的对话。每当用户做了修改时客户端会向服务器发送关于更改的HTTP请求
本项目的服务器部分为 Node.js 编写,客户端部分则为浏览器编写。服务器存储系统数据并将其提供给客户端。它也提供实现客户端系统的文件
![](../Images/00636.jpeg)
服务器保存了为下次聚会提出的对话列表。每个对话包括参与人员姓名、标题和该对话的相关评论。客户端允许用户提出新的对话(将对话添加到列表中)、删除对话和评论已存在的对话。每当用户做了修改时,客户端会向服务器发送关于更改的 HTTP 请求。
我们创建应用程序来展示一个实时视图,来展示目前已经提出的对话和评论。每当某些人在某些地点提交了新的对话或添加新评论时,所有在浏览器中打开页面的人都应该立即看到变化。这个特性略有挑战,网络服务器无法建立到客户端的连接,也没有好方法来知道有哪些客户端现在在查看特定网站。
![](img/21-1.png)
该问题的一个解决方案叫作长时间轮询这恰巧是Node的设计动机之一
我们创建应用来展示一个实时视图,来展示目前已经提出的对话和评论。每当某些人在某些地点提交了新的对话或添加新评论时,所有在浏览器中打开页面的人都应该立即看到变化。这个特性略有挑战,网络服务器无法建立到客户端的连接,也没有好方法来知道有哪些客户端现在在查看特定网站
### 21.2 长轮询
该问题的一个解决方案叫作长时间轮询,这恰巧是 Node 的设计动机之一。
## 长轮询
为了能够立即提示客户端某些信息发生了改变,我们需要建立到客户端的连接。由于通常浏览器无法接受连接,而且客户端通常在路由后面,它无论如何都会拒绝这类连接,因此由服务器初始化连接是不切实际的。
我们可以安排客户端来打开连接并保持该连接,因此服务器可以使用该连接在必要时传送信息。
但HTTP请求只是简单的信息流客户端发送请求服务器返回一条响应就是这样。有一种名为WebSocket的技术受到现代浏览器的支持是的我们可以建立连接并进行任意的数据交换。但如何正确运用这项技术是较为复杂的。
HTTP 请求只是简单的信息流:客户端发送请求,服务器返回一条响应,就是这样。有一种名为 WebSocket 的技术,受到现代浏览器的支持,是的我们可以建立连接并进行任意的数据交换。但如何正确运用这项技术是较为复杂的。
本章我们将会使用一种相对简单的技术长轮询Long Polling。客户端会连续使用定时的HTTP请求向服务器询问新信息而当没有新信息需要报告时服务器会简单地推迟响应。
本章我们将会使用一种相对简单的技术长轮询Long Polling。客户端会连续使用定时的 HTTP 请求向服务器询问新信息,而当没有新信息需要报告时服务器会简单地推迟响应。
只要客户端确保其可以持续不断地建立轮询请求就可以在信息可用之后从服务器快速地接收到信息。例如若Fatma在浏览器中打开了技能分享程序浏览器会发送请求询问是否有更新且等待请求的响应。当Iman在自己的浏览器中提交了关于“极限降滑独轮车”的对话之后。服务器发现Fatma在等待更新请求并将新的对话作为响应发送给待处理的请求。Fatma的浏览器将会接收到数据并更新屏幕展示对话内容。
只要客户端确保其可以持续不断地建立轮询请求,就可以在信息可用之后,从服务器快速地接收到信息。例如,若 Fatma 在浏览器中打开了技能分享程序,浏览器会发送请求询问是否有更新,且等待请求的响应。当 Iman 在自己的浏览器中提交了关于“极限降滑独轮车”的对话之后。服务器发现 Fatma 在等待更新请求并将新的对话作为响应发送给待处理的请求。Fatma 的浏览器将会接收到数据并更新屏幕展示对话内容。
为了防止连接超时(因为连接一定时间不活跃后会被中断),长轮询技术常常为每个请求设置一个最大等待时间,只要超过了这个时间,即使没人有任何需要报告的信息也会返回响应,在此之后,客户端会建立一个新的请求。定期重新发送请求也使得这种技术更具鲁棒性,允许客户端从临时的连接失败或服务器问题中恢复。
使用了长轮询技术的繁忙的服务器可以有成百上千个等待的请求因此也就有这么多个TCP连接处于打开状态。Node简化了多连接的管理工作而不是建立单独线程来控制每个连接这对这样的系统是非常合适的。
使用了长轮询技术的繁忙的服务器,可以有成百上千个等待的请求,因此也就有这么多个 TCP 连接处于打开状态。Node简化了多连接的管理工作而不是建立单独线程来控制每个连接这对这样的系统是非常合适的。
### 21.3 HTTP接口
## HTTP 接口
在我们设计服务器或客户端的代码之前让我们先来思考一下两者均会涉及的一点双方通信的HTTP接口。
在我们设计服务器或客户端的代码之前,让我们先来思考一下两者均会涉及的一点:双方通信的 HTTP 接口。
我们会使用 JSON 作为请求和响应正文的格式就像第二十章中的文件服务器一样我们尝试充分利用HTTP方法。所有接口均以/talks路径为中心。不以/talks开头的路径则用于提供静态文件服务即用于实现客户端系统的HTML和JavaScript代码。
我们会使用 JSON 作为请求和响应正文的格式,就像第二十章中的文件服务器一样,我们尝试充分利用 HTTP 方法。所有接口均以`/talks`路径为中心。不以`/talks`开头的路径则用于提供静态文件服务,即用于实现客户端系统的 HTML JavaScript 代码。
访问/talks的GET请求会返回如下所示的JSON文档。
访问`/talks``GET`请求会返回如下所示的 JSON 文档。
```json
[{"title": "Unituning",
@ -55,9 +57,9 @@
"comment": []}]
```
我们可以发送PUT请求到类似于/talks/Unituning之类的URL上来创建新对话在第二个斜杠后的那部分是对话的名称。PUT请求正文应当包含一个JSON对象其中有一个presenter属性和一个summary属性。
我们可以发送`PUT`请求到类似于`/talks/Unituning`之类的 URL 上来创建新对话,在第二个斜杠后的那部分是对话的名称。`PUT`请求正文应当包含一个 JSON 对象,其中有一个`presenter`属性和一个`summary`属性。
因为对话标题可以包含空格和其他无法正常出现在URL中的字符因此我们必须使用encodeURIComponent函数来编码标题字符串并构建URL。
因为对话标题可以包含空格和其他无法正常出现在 URL 中的字符,因此我们必须使用`encodeURIComponent`函数来编码标题字符串,并构建 URL。
```js
console.log("/talks/" + encodeURIComponent("How to Idle"));
@ -66,8 +68,6 @@ console.log("/talks/" + encodeURIComponent("How to Idle"));
下面这个请求用于创建关于“空转”的对话。
![](../Images/00639.jpeg)![](../Images/00640.jpeg)
```http
PUT /talks/How%20to%20Idle HTTP/1.1
Content-Type: application/json
@ -77,9 +77,9 @@ Content-Length: 92
"summary": "Standing still on a unicycle"}
```
我们也可以使用GET请求通过这些URL获取对话的JSON数据或使用DELETE请求通过这些URL删除对话。
我们也可以使用`GET`请求通过这些 URL 获取对话的 JSON 数据,或使用`DELETE`请求通过这些 URL 删除对话。
若想在对话中添加一条评论,可以向诸如/talks/Unituning/comments的URL发送POST请求JSON 正文包含一个author属性和message属性。
为了在对话中添加一条评论,可以向诸如`/talks/Unituning/comments`的 URL 发送`POST`请求JSON 正文包含`author`属性和`message`属性。
```http
POST /talks/Unituning/comments HTTP/1.1
@ -90,7 +90,7 @@ Content-Length: 72
"message": "Will you talk about raising a cycle?"}
```
为了支持长轮询,如果没有新的信息可用,发送到/talks的GET请求可能会包含额外的标题通知服务器延迟响应。 我们将使用通常用于管理缓存的一对协议头:`ETag``If-None-Match`
为了支持长轮询,如果没有新的信息可用,发送到`/talks``GET`请求可能会包含额外的标题,通知服务器延迟响应。 我们将使用通常用于管理缓存的一对协议头:`ETag``If-None-Match`
服务器可能在响应中包含`ETag`(“实体标签”)协议头。 它的值是标识资源当前版本的字符串。 当客户稍后再次请求该资源时,可以通过包含一个`If-None-Match`头来进行条件请求,该头的值保存相同的字符串。 如果资源没有改变,服务器将响应状态码 304这意味着“未修改”告诉客户端它的缓存版本仍然是最新的。 当标签与服务器不匹配时,服务器正常响应。
@ -116,19 +116,19 @@ Content-Length: 295
这里描述的协议并没有任何访问控制。每个人都可以评论、修改对话或删除对话。因为因特网中充满了流氓,因此将这类没有进一步保护的系统放在网络上最后可能并不是很好。
### 21.4 服务器
## 服务器
让我们开始构建程序的服务器部分。本节的代码可以在Node.js中执行。
让我们开始构建程序的服务器部分。本节的代码可以在 Node.js 中执行。
#### 21.4.1 路由
### 路由
我们的服务器会使用createServer来启动HTTP服务器。在处理新请求的函数中我们必须区分我们支持的请求的类型根据方法和路径确定。我们可以使用一长串的if语句完成该任务但还存在一种更优雅的方式。
我们的服务器会使用`createServer`来启动 HTTP 服务器。在处理新请求的函数中,我们必须区分我们支持的请求的类型(根据方法和路径确定)。我们可以使用一长串的`if`语句完成该任务,但还存在一种更优雅的方式。
路由可以作为帮助把请求调度传给能处理该请求的函数路径可以和正则表达式/^\/talks\/[^\/]+$/匹配(后面有对话名称的`/talks/`的PUT请求应当由指定函数处理。此外路由器可以帮助我们提取路径中有意义的部分,在本例中会将对话的标题(包裹在正则表达式的括号之中)传递给处理器函数。
路由可以作为帮助把请求调度传给能处理该请求的函数。路径匹配正则表达式`/^\/talks\/([^\/]+)$/``/talks/`带着对话名称)的`PUT`请求,应当由指定函数处理。此外,路由可以帮助我们提取路径中有意义的部分,在本例中会将对话的标题(包裹在正则表达式的括号之中)传递给处理器函数。
在NPM中有许多优秀的路由包,但这里我们自己编写一个路由来展示其原理。
NPM 中有许多优秀的路由包,但这里我们自己编写一个路由来展示其原理。
这里给出router.js我们随后将在服务器模块中使用require获取该模块。
这里给出`router.js`,我们随后将在服务器模块中使用`require`获取该模块。
```js
const {parse} = require("url");
@ -154,19 +154,19 @@ module.exports = class Router {
};
```
该模块导出Router类。我们可以使用路由对象的ad方法来注册一个新的处理器并使用resolve方法解析请求。
该模块导出`Router`类。我们可以使用路由对象的`add`方法来注册一个新的处理器,并使用`resolve`方法解析请求。
找到处理器之后,后者会返回一个响应,否则为`null`。它会逐个尝试路由根据定义顺序排序当找到一个匹配的路由时返回true。
找到处理器之后,后者会返回一个响应,否则为`null`。它会逐个尝试路由(根据定义顺序排序),当找到一个匹配的路由时返回`true`
路由会使用`context`值调用处理器函数(这里是服务器实例),将请求对象中的字符串,与已定义分组中的正则表达式匹配。传递给处理的字符串必须进行URL解码因为原始URL中可能包含%20style风格的代码。
路由会使用`context`值调用处理器函数(这里是服务器实例),将请求对象中的字符串,与已定义分组中的正则表达式匹配。传递给处理器的字符串必须进行 URL 解码,因为原始 URL 中可能包含`%20`风格的代码。
#### 21.4.2 文件服务
### 文件服务
当请求无法匹配路由中定义的任何请求类型时服务器必须将其解释为请求位于public目录下的某个文件。服务器可以使用第20章中定义的文件服务器来提供文件服务但我们并不需要也不想对文件支持PUT和DELETE请求且我们想支持类似于缓存等高级特性。因此让我们使用NPM中更为可靠且经过充分测试的静态文件服务器。
当请求无法匹配路由中定义的任何请求类型时,服务器必须将其解释为请求位于`public`目录下的某个文件。服务器可以使用第二十章中定义的文件服务器来提供文件服务,但我们并不需要也不想对文件支持 PUT DELETE 请求,且我们想支持类似于缓存等高级特性。因此让我们使用 NPM 中更为可靠且经过充分测试的静态文件服务器。
我选择了ecstatic。它并不是NPM中唯一的此类服务但它能够完美工作且符合我们的意图。ecstatic模块导出了一个函数我们可以调用该函数并传递一个配置对象来生成一个请求处理函数。我们使用root选项告知服务器文件搜索位置。
我选择了`ecstatic`。它并不是 NPM 中唯一的此类服务,但它能够完美工作且符合我们的意图。`ecstatic`模块导出了一个函数,我们可以调用该函数,并传递一个配置对象来生成一个请求处理函数。我们使用`root`选项告知服务器文件搜索位置。
```
```js
const {createServer} = require("http");
const Router = require("./router");
const ecstatic = require("ecstatic");
@ -209,11 +209,11 @@ class SkillShareServer {
它使用上一章中的文件服务器的类似约定来处理响应 - 处理器返回`Promise`,可解析为描述响应的对象。 它将服务器包装在一个对象中,它也维护它的状态。
#### 21.4.3 将对话作为资源
### 作为资源的对话
已提出的对话存储在服务器的`talks`属性中,这是一个对象,属性名称是对话标题。这些对话会展现为/talks/[title]下的HTTP资源因此我们需要将处理器添加我们的路由中供客户端选择,来实现不同的方法。
已提出的对话存储在服务器的`talks`属性中,这是一个对象,属性名称是对话标题。这些对话会展现为`/talks/[title]`下的 HTTP 资源,因此我们需要将处理器添加我们的路由中供客户端选择,来实现不同的方法。
处理获取GET对话请求的函数必须查找对话并使用对话的JSON数据作为响应若不存在则返回404错误响应码。
获取(`GET`)单个对话的请求处理器,必须查找对话并使用对话的 JSON 数据作为响应,若不存在则返回 404 错误响应码。
```js
const talkPath = /^\/talks\/([^\/]+)$/;
@ -228,7 +228,7 @@ router.add("GET", talkPath, async (server, title) => {
});
```
删除对话时将其从talks对象中删除即可。
删除对话时,将其从`talks`对象中删除即可。
```js
router.add("DELETE", talkPath, async (server, title) => {
@ -242,7 +242,7 @@ router.add("DELETE", talkPath, async (server, title) => {
我们将在稍后定义`updated`方法,它通知等待有关更改的长轮询请求。
为了获取请求正文的内容我们定义一个名为readStream的函数从可读流中读取所有内容并返回解析为字符串的`Promise`
为了获取请求正文的内容,我们定义一个名为`readStream`的函数,从可读流中读取所有内容,并返回解析为字符串的`Promise`
```js
function readStream(stream) {
@ -255,9 +255,9 @@ function readStream(stream) {
}
```
需要读取响应正文的函数是PUT的处理器用户使用它创建新对话。该函数需要检查数据中是否有presenter和summary属性这些属性都是字符串。任何来自外部的数据都可能是无意义的我们不希望错误请求到达时会破坏我们的内部数据模型或者导致服务崩溃。
需要读取响应正文的函数是`PUT`的处理器,用户使用它创建新对话。该函数需要检查数据中是否有`presenter``summary`属性,这些属性都是字符串。任何来自外部的数据都可能是无意义的,我们不希望错误请求到达时会破坏我们的内部数据模型,或者导致服务崩溃。
若数据看起来合法处理器会将对话转化为对象存储在talks对象中如果有标题相同的对话存在则覆盖并再次调用updated。
若数据看起来合法,处理器会将对话转化为对象,存储在`talks`对象中,如果有标题相同的对话存在则覆盖,并再次调用`updated`
```js
router.add("PUT", talkPath,
@ -281,7 +281,7 @@ router.add("PUT", talkPath,
});
```
在对话中添加评论也是类似的。我们使用readStream来获取请求内容验证请求数据若看上去合法则将其存储为评论。
在对话中添加评论也是类似的。我们使用`readStream`来获取请求内容,验证请求数据,若看上去合法,则将其存储为评论。
```js
router.add("POST", /^\/talks\/([^\/]+)\/comments$/,
@ -305,11 +305,11 @@ router.add("POST", /^\/talks\/([^\/]+)\/comments$/,
});
```
尝试向不存在的对话中添加评论会返回404错误。
尝试向不存在的对话中添加评论会返回 404 错误。
#### 21.4.4 长轮询支持
### 长轮询支持
服务器中最值得探讨的方面是处理长轮询的部分代码。当URL为/talks的GET请求到来时它可能是一个常规请求或一个长轮询请求。
服务器中最值得探讨的方面是处理长轮询的部分代码。当 URL `/talks``GET`请求到来时,它可能是一个常规请求或一个长轮询请求。
我们可能在很多地方,将对话列表发送给客户端,因此我们首先定义一个简单的辅助函数,它构建这样一个数组,并在响应中包含`ETag`协议头。
@ -327,7 +327,7 @@ SkillShareServer.prototype.talkResponse = function() {
};
```
处理程序本身需要查看请求头,来查看是否存在`If-None-Match``Prefer`标头。 Node 在其小写名称下存储协议头,根据规定其名称是不区分大小写的。
处理本身需要查看请求头,来查看是否存在`If-None-Match``Prefer`标头。 Node 在其小写名称下存储协议头,根据规定其名称是不区分大小写的。
```js
router.add("GET", /^\/talks$/, async (server, request) => {
@ -379,13 +379,13 @@ SkillShareServer.prototype.updated = function() {
new SkillShareServer(Object.create(null)).start(8000);
```
### 21.5 客户端
## 客户端
技能分享网站的客户端部分由三个文件组成:微型 HTML 页面、样式表以及 JavaScript 文件。
#### 21.5.1 HTML
### HTML
在网络服务器提供文件服务时有一种广为使用的约定是当请求直接访问与目录对应的路径时返回名为index.html的文件。我们使用的文件服务模块ecstatic就支持这种约定。当请求路径为/时,服务器会搜索文件./public/index.html./public是我们赋予的根目录若文件存在则返回文件。
在网络服务器提供文件服务时,有一种广为使用的约定是:当请求直接访问与目录对应的路径时,返回名为`index.html`的文件。我们使用的文件服务模块`ecstatic`就支持这种约定。当请求路径为/时,服务器会搜索文件`./public/index.html``./public`是我们赋予的根目录),若文件存在则返回文件。
因此,若我们希望浏览器指向我们服务器时展示某个特定页面,我们将其放在`public/index.html`中。这就是我们的`index`文件。
@ -406,7 +406,7 @@ new SkillShareServer(Object.create(null)).start(8000);
### 动作
应用程序状态由对话列表和用户名称组成,我们将它存储在一个`{talks, user}`对象中。 我们不允许用户界面直接操作状态或发送 HTTP 请求。 反之,它可能会触发动作,它描述用户正在尝试做什么。
应用状态由对话列表和用户名称组成,我们将它存储在一个`{talks, user}`对象中。 我们不允许用户界面直接操作状态或发送 HTTP 请求。 反之,它可能会触发动作,它描述用户正在尝试做什么。
```js
function handleAction(state, action) {
@ -462,7 +462,7 @@ function talkURL(title) {
}
```
当请求失败时我们不希望我们的页面丝毫不变不给予任何提示。因此我们定义一个函数名为reportError至少在发生错误时向用户展示一个对话框。
当请求失败时,我们不希望我们的页面丝毫不变,不给予任何提示。因此我们定义一个函数,名为`reportError`,至少在发生错误时向用户展示一个对话框。
```js
function reportError(error) {
@ -470,9 +470,9 @@ function reportError(error) {
}
```
#### 渲染组件
### 渲染组件
我们将使用一个方法,类似于我们在第十九章中所见,将应用程序拆分为组件。 但由于某些组件不需要更新,或者在更新时总是完全重新绘制,所以我们不将它们定义为类,而是直接返回 DOM 节点的函数。 例如,下面是一个组件,显示用户可以向它输入名称的字段的:
我们将使用一个方法,类似于我们在第十九章中所见,将应用拆分为组件。 但由于某些组件不需要更新,或者在更新时总是完全重新绘制,所以我们不将它们定义为类,而是直接返回 DOM 节点的函数。 例如,下面是一个组件,显示用户可以向它输入名称的字段的:
```js
function renderUserField(name, dispatch) {
@ -486,7 +486,7 @@ function renderUserField(name, dispatch) {
}
```
用于构建DOM元素的`elt`函数是我们在第十九章中使用的函数。
用于构建 DOM 元素的`elt`函数是我们在第十九章中使用的函数。
类似的函数用于渲染对话,包括评论列表和添加新评论的表单。
@ -555,7 +555,7 @@ function renderTalkForm(dispatch) {
### 轮询
为了启动应用程序,我们需要对话的当前列表。 由于初始加载与长轮询过程密切相关 -- 轮询时必须使用来自加载的`ETag` -- 我们将编写一个函数来不断轮询服务器的`/ talks`,并且在新的对话集可用时,调用回调函数。
为了启动应用,我们需要对话的当前列表。 由于初始加载与长轮询过程密切相关 -- 轮询时必须使用来自加载的`ETag` -- 我们将编写一个函数来不断轮询服务器的`/ talks`,并且在新的对话集可用时,调用回调函数。
```js
async function pollTalks(update) {
@ -581,7 +581,7 @@ async function pollTalks(update) {
这是一个`async`函数,因此循环和等待请求更容易。 它运行一个无限循环,每次迭代中,通常检索对话列表。或者,如果这不是第一个请求,则带有使其成为长轮询请求的协议头。
当请求失败时,函数会等待一会儿,然后再次尝试。 这样,如果的网络连接断了一段时间然后又恢复,应用程序可以恢复并继续更新。 通过`setTimeout`解析的`Promise`,是强制`async`函数等待的方法。
当请求失败时,函数会等待一会儿,然后再次尝试。 这样,如果的网络连接断了一段时间然后又恢复,应用可以恢复并继续更新。 通过`setTimeout`解析的`Promise`,是强制`async`函数等待的方法。
当服务器回复 304 响应时,这意味着长轮询请求超时,所以函数应该立即启动下一个请求。 如果响应是普通的 200 响应,它的正文将当做 JSON 而读取并传递给回调函数,并且它的`ETag`协议头的值为下一次迭代而存储。
@ -643,18 +643,18 @@ runApp();
若你执行服务器并同时为`localhost:8000/`打开两个浏览器窗口,你可以看到在一个窗口中执行动作时,另一个窗口中会立即做出反应。
### 21.6 习题
## 习题
下面的习题涉及修改本章中定义的系统。为了使用该系统进行工作,请确保首先下载[代码](http://eloquentjavascript.net/code/skillshare.zip),安装了 [Node](http://nodejs.org/),并使用`npm install`安装了项目的所有依赖。
#### 21.6.1 磁盘持久化
### 磁盘持久化
技能分享服务只将数据存储在内存中。这就意味着当服务崩溃或以为任何原因重启时,所有的对话和评论都会丢失。
扩展服务使得其将对话数据存储到磁盘上,并在程序重启时自动重新加载数据。不要担心效率,只要用最简单的代码让其可以工作即可。
#### 21.6.2 重置评论字段
### 重置评论字段
由于我们常常无法在DOM节点中找到唯一替换的位置因此整批地重绘对话是个很好的工作机制。但这里有个例外若你开始在对话的评论字段中输入一些文字而在另一个窗口向同一条对话添加了一条评论那么第一个窗口中的字段就会被重绘会移除掉其内容和焦点。
由于我们常常无法在 DOM 节点中找到唯一替换的位置,因此整批地重绘对话是个很好的工作机制。但这里有个例外,若你开始在对话的评论字段中输入一些文字,而在另一个窗口向同一条对话添加了一条评论,那么第一个窗口中的字段就会被重绘,会移除掉其内容和焦点。
在激烈的讨论中,多人同时添加评论,这将是非常烦人的。 你能想出办法解决它吗?

BIN
img/21-0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
img/21-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB