type
status
date
slug
summary
tags
category
icon
password
Edited
Sep 15, 2022 01:54 PM
Created
Sep 15, 2022 01:47 PM
Fetch
url—— 要访问的 URL。
options—— 可选参数:method,header 等。
没有
options,这就是一个简单的 GET 请求,下载 url 的内容。响应的属性:
response.status—— response 的 HTTP 状态码,
response.ok—— HTTP 状态码为 200-299,则为true。
response.headers—— 类似于 Map 的带有 HTTP header 的对象。
获取 response body 的方法:
response.text()—— 读取 response,并以文本形式返回 response,
response.json()—— 将 response 解析为 JSON 对象形式,
response.formData()—— 以FormData对象(multipart/form-data编码,参见下一章)的形式返回 response,
response.blob()—— 以 Blob(具有类型的二进制数据)形式返回 response,
response.arrayBuffer()—— 以 ArrayBuffer(低级别的二进制数据)形式返回 response。
第一阶段,当服务器发送了响应头(response header),
fetch 返回的 promise 就使用内建的 Response class 对象来对响应头进行解析。第二阶段,为了获取 response body,我们需要使用一个其他的方法调用。
设置请求头:
获取响应头:
Post:
method—— HTTP 方法,例如POST,
body—— request body,其中之一:- 字符串(例如 JSON 编码的),
FormData对象,以multipart/form-data形式发送数据,Blob/BufferSource发送二进制数据,- URLSearchParams,以
x-www-form-urlencoded编码形式发送数据,很少使用。
FormData
FormData 对象表示 HTML 表单数据的对象。
如果提供了 HTML
form 元素,它会自动捕获 form 元素字段。FormData 的特殊之处在于网络方法(network methods),例如 fetch 可以接受一个 FormData 对象作为 body。它会被编码并发送出去,带有 Content-Type: multipart/form-data。方法:
我们可以使用以下方法修改
FormData 中的字段:formData.append(name, value)—— 添加具有给定name和value的表单字段,
formData.append(name, blob, fileName)—— 添加一个字段,就像它是<input type="file">,第三个参数fileName设置文件名(而不是表单字段名),因为它是用户文件系统中文件的名称,
formData.delete(name)—— 移除带有给定name的字段,
formData.get(name)—— 获取带有给定name的字段值,
formData.has(name)—— 如果存在带有给定name的字段,则返回true,否则返回false。
.set 移除所有具有给定 name 的字段,然后附加一个新字段formData.set(name, value),
formData.set(name, blob, fileName)。
表单始终以
Content-Type: multipart/form-data 来发送数据,这个编码允许发送文件。因此 <input type="file"> 字段也能被发送Fetch 下载进度
要跟踪下载进度,我们可以使用
response.body 属性。它是 ReadableStream —— 一个特殊的对象,它可以逐块(chunk)提供 body。await reader.read() 调用的结果是一个具有两个属性的对象:done—— 当读取完成时为true,否则为false。
value—— 字节的类型化数组:Uint8Array。
示例:
Fetch 中止(Abort)
控制器是一个极其简单的对象。
- 它具有单个方法
abort(),
- 和单个属性
signal,我们可以在这个属性上设置事件监听器。
当
abort() 被调用时:controller.signal就会触发abort事件。
controller.signal.aborted属性变为true。
通常,我们需要处理两部分:
- 一部分是通过在
controller.signal上添加一个监听器,来执行可取消操作。
- 另一部分是触发取消:在需要的时候调用
controller.abort()。
与 fetch 一起使用
Fetch 跨源请求
“CORS”:跨源资源共享(Cross-Origin Resource Sharing)
安全请求
有两种类型的跨源请求:
- 安全请求。
- 所有其他请求。
安全请求很简单,所以我们先从它开始。
如果一个请求满足下面这两个条件,则该请求是安全的:
- 安全的方法:GET,POST 或 HEAD
- 安全的 header —— 仅允许自定义下列 header:
Accept,Accept-Language,Content-Language,Content-Type的值为application/x-www-form-urlencoded,multipart/form-data或text/plain。
任何其他请求都被认为是“非安全”请求。例如,具有
PUT 方法或 API-Key HTTP-header 的请求就不是安全请求。本质区别在于,可以使用
<form> 或 <script> 进行安全请求,而无需任何其他特殊方法。安全请求的CORS
如果一个请求是跨源的,浏览器始终会向其添加
Origin header。服务器可以检查
Origin,如果同意接受这样的请求,就会在响应中添加一个特殊的 header Access-Control-Allow-Origin。该 header 包含了允许的源,或者一个星号 *。然后响应成功,否则报错浏览器在这里扮演受被信任的中间人的角色:
- 它确保发送的跨源请求带有正确的
Origin。
- 它检查响应中的许可
Access-Control-Allow-Origin,如果存在,则允许 JavaScript 访问响应,否则将失败并报错。

安全的 response header
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
列表中没有
Content-Length header!要授予 JavaScript 对任何其他 response header 的访问权限,服务器必须发送
Access-Control-Expose-Headers header。它包含一个以逗号分隔的应该被设置为可访问的非安全 header 名称列表。”非安全“请求
首先,它会先发送一个初步的、所谓的“预检(preflight)”请求,来请求许可。
预检请求使用
OPTIONS 方法,它没有 body,但是有三个 header:Access-Control-Request-Methodheader 带有非安全请求的方法。
Access-Control-Request-Headersheader 提供一个以逗号分隔的非安全 HTTP-header 列表。
如果服务器同意处理请求,那么它会进行响应,此响应的状态码应该为 200,没有 body,具有 header:
Access-Control-Allow-Origin必须为或进行请求的源(例如https://javascript.info)才能允许此请求。
Access-Control-Allow-Methods必须具有允许的方法。
Access-Control-Allow-Headers必须具有一个允许的 header 列表。
- 另外,header
Access-Control-Max-Age可以指定缓存此权限的秒数。因此,浏览器不是必须为满足给定权限的后续请求发送预检。

示例:
- Step 1 预检请求(preflight request)
- 方法:
OPTIONS。 - 路径 —— 与主请求完全相同:
/service.json。 - 特殊跨源头:
Origin—— 来源。Access-Control-Request-Method—— 请求方法。Access-Control-Request-Headers—— 以逗号分隔的“非安全” header 列表。
在发送我们的请求前,浏览器会自己发送如下所示的预检请求:
- Step 2 预检响应(preflight response)
Access-Control-Allow-Origin: https://javascript.infoAccess-Control-Allow-Methods: PATCHAccess-Control-Allow-Headers: Content-Type,API-Key。
服务应响应状态 200 和 header:
这将允许后续通信,否则会触发错误。
- Step 3 实际请求(actual request)
预检成功后,浏览器现在发出主请求。这里的过程与安全请求的过程相同。
- Step 4 实际响应(actual response)
服务器不应该忘记在主响应中添加
Access-Control-Allow-Origin。成功的预检并不能免除此要求Fetch API
这是所有可能的
fetch 选项及其默认值(注释中标注了可选值)的完整列表:URL 对象
内建的 URL 类提供了用于创建和解析 URL 的便捷接口。
url—— 完整的 URL,或者仅路径(如果设置了 base),
base—— 可选的 base URL:如果设置了此参数,且参数url只有路径,则会根据这个base生成 URL。
下面这两个 URL 是一样的:
URL 组件的备忘单:

SearchParams
url.searchParams,URLSearchParams 类型的对象。它为搜索参数提供了简便的方法:
append(name, value)—— 按照name添加参数,
delete(name)—— 按照name移除参数,
get(name)—— 按照name获取参数,
getAll(name)—— 获取相同name的所有参数(这是可行的,例如?user=John&user=Pete),
has(name)—— 按照name检查参数是否存在,
set(name, value)—— set/replace 参数,
sort()—— 按 name 对参数进行排序,很少使用,
- ……并且它是可迭代的,类似于
Map。
编码(encoding)
RFC3986 标准定义了 URL 中允许哪些字符,不允许哪些字符。
那些不被允许的字符必须被编码,例如非拉丁字母和空格 —— 用其 UTF-8 代码代替,前缀为
%,例如 %20(由于历史原因,空格可以用 + 编码,但这是一个例外)。好消息是
URL 对象会自动处理这些。我们仅需提供未编码的参数,然后将 URL 转换为字符串编码字符串
如果使用字符串,则需要手动编码/解码特殊字符。
下面是用于编码/解码 URL 的内建函数:
encodeURI—— 编码整个 URL。
decodeURI—— 解码为编码前的状态。
encodeURIComponent—— 编码 URL 组件,例如搜索参数,或者 hash,或者 pathname。
decodeURIComponent—— 解码为编码前的状态。
区别:
encodeURI仅编码 URL 中完全禁止的字符。
encodeURIComponent也编码这类字符,此外,还编码#,$,&,+,,,/,:,;,=,?和@字符。
所以,对于一个 URL 整体,我们可以使用
encodeURI而对于 URL 参数,我们应该改用
encodeURIComponentURL 和 URLSearchParams 基于最新的 URL 规范:RFC3986,而 encode* 函数是基于过时的 RFC2396。XMLHttpRequest
XMLHttpRequest 有两种执行模式:同步(synchronous)和异步(asynchronous)。
异步模式
- 创建
XMLHttpRequest:
- 初始化它,通常就在
new XMLHttpRequest之后: method—— HTTP 方法。通常是"GET"或"POST"。URL—— 要请求的 URL,通常是一个字符串,也可以是 URL 对象。async—— 如果显式地设置为false,那么请求将会以同步的方式处理,我们稍后会讲到它。user,password—— HTTP 基本身份验证(如果需要的话)的登录名和密码。
此方法指定请求的主要参数:
请注意,
open 调用与其名称相反,不会建立连接。它仅配置请求,而网络活动仅以 send 调用开启。- 发送请求。
这个方法会建立连接,并将请求发送到服务器。可选参数
body 包含了 request body。- 监听
xhr事件以获取响应。 load—— 当请求完成(即使 HTTP 状态为 400 或 500 等),并且响应已完全下载。error—— 当无法发出请求,例如网络中断或者无效的 URL。progress—— 在下载响应期间定期触发,报告已经下载了多少。
这三个事件是最常用的:
一旦服务器有了响应,我们可以在以下
xhr 属性中接收结果:statusHTTP 状态码(一个数字):
200,404,403 等,如果出现非 HTTP 错误,则为 0。statusTextHTTP 状态消息(一个字符串):状态码为
200 对应于 OK,404 对应于 Not Found,403 对应于 Forbidden。response(旧脚本可能用的是 responseText)服务器 response body。
响应:
我们可以使用
xhr.responseType 属性来设置响应格式:""(默认)—— 响应格式为字符串,
"text"—— 响应格式为字符串,
"arraybuffer"—— 响应格式为ArrayBuffer(对于二进制数据,请参见 ArrayBuffer,二进制数组),
"blob"—— 响应格式为Blob(对于二进制数据,请参见 Blob),
"document"—— 响应格式为 XML document(可以使用 XPath 和其他 XML 方法)或 HTML document(基于接收数据的 MIME 类型)
"json"—— 响应格式为 JSON(自动解析)。
readyState:
XMLHttpRequest 的状态(state)会随着它的处理进度变化而变化。可以通过 xhr.readyState 来了解当前状态。XMLHttpRequest 对象以 0 → 1 → 2 → 3 → … → 3 → 4 的顺序在它们之间转变。每当通过网络接收到一个数据包,就会重复一次状态 3。我们可以使用
readystatechange 事件来跟踪它们调用
xhr.abort() 终止请求同步请求
如果在
open 方法中将第三个参数 async 设置为 false,那么请求就会以同步的方式进行。换句话说,JavaScript 执行在
send() 处暂停,并在收到响应后恢复执行。这有点儿像 alert 或 prompt 命令。HTTP 配置
header:
XMLHttpRequest 允许发送自定义 header,并且可以从响应中读取 header。HTTP-header 有三种方法:
setRequestHeader(name, value)
使用给定的
name 和 value 设置 request header。XMLHttpRequest 是不能撤销 setRequestHeader。getResponseHeader(name)
获取具有给定
name 的 header(Set-Cookie 和 Set-Cookie2 除外)getAllResponseHeaders()
返回除
Set-Cookie 和 Set-Cookie2 外的所有 response header。header 之间的换行符始终为
"\r\n"获取所有header:
POST & FormData:
可以使用内建的 FormData 对象。
xhr.open('POST', ...)—— 使用POST方法。
xhr.send(formData)将表单发送到服务器。
上传进度
progress 事件仅在下载阶段触发。它专门用于跟踪上传事件:xhr.upload。它会生成事件,类似于
xhr,但是 xhr.upload 仅在上传时触发它们:loadstart—— 上传开始。
progress—— 上传期间定期触发。
abort—— 上传中止。
error—— 非 HTTP 错误。
load—— 上传成功完成。
timeout—— 上传超时(如果设置了timeout属性)。
loadend—— 上传完成,无论成功还是 error。
示例:
可恢复的文件上传
长轮询
长轮询是与服务器保持持久连接的最简单的方式,它不使用任何特定的协议,例如 WebSocket 或者 Server Sent Event。
常规轮询
从服务器获取新信息的最简单的方式是定期轮询。也就是说,定期向服务器发出请求:“你好,我在这儿,你有关于我的任何信息吗?”例如,每 10 秒一次。
作为响应,服务器首先通知自己,客户端处于在线状态,然后 —— 发送目前为止的消息包。
这可行,但是也有些缺点:
- 消息传递的延迟最多为 10 秒(两个请求之间)。
- 即使没有消息,服务器也会每隔 10 秒被请求轰炸一次,即使用户切换到其他地方或者处于休眠状态,也是如此。就性能而言,这是一个很大的负担。
长轮询
所谓“长轮询”是轮询服务器的一种更好的方式。
它也很容易实现,并且可以无延迟地传递消息。
其流程为:
- 请求发送到服务器。
- 服务器在有消息之前不会关闭连接。
- 当消息出现时 —— 服务器将对其请求作出响应。
- 浏览器立即发出一个新的请求。
对于此方法,浏览器发出一个请求并与服务器之间建立起一个挂起的(pending)连接的情况是标准的。仅在有消息被传递时,才会重新建立连接。

实现:
WebSocket
在 RFC 6455 规范中描述的
WebSocket 协议,提供了一种在浏览器和服务器之间建立持久连接来交换数据的方法。数据可以作为“数据包”在两个方向上传递,而无需中断连接也无需额外的 HTTP 请求。要打开一个 WebSocket 连接,我们需要在 url 中使用特殊的协议
ws 创建 new WebSocket同样也有一个加密的
wss:// 协议。类似于 WebSocket 中的 HTTPS。始终使用
wss://wss:// 协议不仅是被加密的,而且更可靠。一旦 socket 被建立,我们就应该监听 socket 上的事件。一共有 4 个事件:
open—— 连接已建立,
message—— 接收到数据,
error—— WebSocket 错误,
close—— 连接已关闭。
WebSocket 方法:
socket.send(data),
socket.close([code], [reason])。
连接状态
要获取连接状态,可以通过带有值的
socket.readyState 属性:0—— “CONNECTING”:连接还未建立,
1—— “OPEN”:通信中,
2—— “CLOSING”:连接关闭中,
3—— “CLOSED”:连接已关闭。
深入
当
new WebSocket(url) 被创建后,它将立即开始连接。在连接期间,浏览器(使用 header)问服务器:“你支持 WebSocket 吗?”如果服务器回复说“我支持”,那么通信就以 WebSocket 协议继续进行,该协议根本不是 HTTP。

请求的浏览器 header 示例:
Origin—— 客户端页面的源,例如https://javascript.info。WebSocket 对象是原生支持跨源的。没有特殊的 header 或其他限制。旧的服务器无法处理 WebSocket,因此不存在兼容性问题。但Originheader 很重要,因为它允许服务器决定是否使用 WebSocket 与该网站通信。
Connection: Upgrade—— 表示客户端想要更改协议。
Upgrade: websocket—— 请求的协议是 “websocket”。
Sec-WebSocket-Key—— 浏览器随机生成的安全密钥。
Sec-WebSocket-Version—— WebSocket 协议版本,当前为 13。
如果服务器同意切换为 WebSocket 协议,服务器应该返回响应码 101:
这里
Sec-WebSocket-Accept 是 Sec-WebSocket-Key,是使用特殊的算法重新编码的。浏览器使用它来确保响应与请求相对应。扩展和子协议
WebSocket 可能还有其他 header,
Sec-WebSocket-Extensions 和 Sec-WebSocket-Protocol,它们描述了扩展和子协议。例如:
Sec-WebSocket-Extensions: deflate-frame表示浏览器支持数据压缩。扩展与传输数据有关,扩展了 WebSocket 协议的功能。Sec-WebSocket-Extensionsheader 由浏览器自动发送,其中包含其支持的所有扩展的列表。
Sec-WebSocket-Protocol: soap, wamp表示我们不仅要传输任何数据,还要传输 SOAP 或 WAMP(“The WebSocket Application Messaging Protocol”)协议中的数据。WebSocket 子协议已经在 IANA catalogue 中注册。因此,此 header 描述了我们将要使用的数据格式。
这个可选的 header 是使用
new WebSocket 的第二个参数设置的。它是子协议数组,例如,如果我们想使用 SOAP 或 WAMP:数据传输
WebSocket 通信由 “frames”(即数据片段)组成,可以从任何一方发送,并且有以下几种类型:
- “text frames” —— 包含各方发送给彼此的文本数据。
- “binary data frames” —— 包含各方发送给彼此的二进制数据。
- “ping/pong frames” 被用于检查从服务器发送的连接,浏览器会自动响应它们。
- 还有 “connection close frame” 以及其他服务 frames。
在浏览器里,我们仅直接使用文本或二进制 frames。
WebSocket
.send() 方法可以发送文本或二进制数据。socket.send(body) 调用允许 body 是字符串或二进制格式,包括 Blob,ArrayBuffer 等。不需要额外的设置:直接发送它们就可以了。它是由
socket.binaryType 属性设置的,默认为 "blob",因此二进制数据通常以 Blob 对象呈现。限速
我们可以反复地调用
socket.send(data)。但是数据将会缓冲(储存)在内存中,并且只能在网速允许的情况下尽快将数据发送出去。socket.bufferedAmount 属性储存了目前已缓冲的字节数,等待通过网络发送。我们可以检查它以查看 socket 是否真的可用于传输。
Server Sent Events
Server-Sent Events 规范描述了一个内建的类
EventSource,它能保持与服务器的连接,并允许从中接收事件。与
WebSocket 类似,其连接是持久的。两者之间有几个重要的区别:

使用它的主要原因:简单。在很多应用中,WebSocket有点大材小用。
使用
要开始接收消息,我们只需要创建
new EventSource(url) 即可。浏览器将会连接到
url 并保持连接打开,等待事件。服务器响应状态码应该为 200,header 为
Content-Type: text/event-stream,然后保持此连接并以一种特殊的格式写入消息,就像这样:data:后为消息文本,冒号后面的空格是可选的。
- 消息以双换行符
\n\n分隔。
- 要发送一个换行
\n,我们可以在要换行的位置立即再发送一个data:(上面的第三条消息)。
在实际开发中,复杂的消息通常是用 JSON 编码后发送。换行符在其中编码为
\n,因此不需要多行 data: 消息。对于每个这样的消息,都会生成
message 事件重新连接
创建之后,
new EventSource 连接到服务器,如果连接断开 —— 则重新连接。这非常方便,我们不用去关心重新连接的事情。
每次重新连接之间有一点小的延迟,默认为几秒钟。
服务器可以使用
retry: 来设置需要的延迟响应时间(以毫秒为单位)。- 如果服务器想要浏览器停止重新连接,那么它应该使用 HTTP 状态码 204 进行响应。
- 如果浏览器想要关闭连接,则应该调用
eventSource.close():
当连接最终被关闭时,就无法“重新打开”它。如果我们想要再次连接,只需要创建一个新的
EventSource。消息 id
每条消息都应该有一个
id 字段当收到具有
id 的消息时,浏览器会:- 将属性
eventSource.lastEventId设置为其值。
- 重新连接后,发送带有
id的 headerLast-Event-ID,以便服务器可以重新发送后面的消息。
把
id: 放在 data: 后请注意:
id 被服务器附加到 data 消息后,以确保在收到消息后 lastEventId 会被更新。连接状态:readyState
EventSource 对象有 readyState 属性,该属性具有下列值之一:对象创建完成或者连接断开后,它始终是
EventSource.CONNECTING(等于 0)。事件
默认情况下
EventSource 对象生成三个事件:message—— 收到消息,可以用event.data访问。
open—— 连接已打开。
error—— 无法建立连接,例如,服务器返回 HTTP 500 状态码。
服务器可以在事件开始时使用
event: ... 指定另一种类型事件。要处理自定义事件,我们必须使用
addEventListener 而非 onmessage:Node端 简单使用
服务器响应格式:
服务器发送由
\n\n 分隔的消息。一条消息可能有以下字段:
data:—— 消息体(body),一系列多个data被解释为单个消息,各个部分之间由\n分隔。
id:—— 更新lastEventId,重连时以Last-Event-ID发送此 id。
retry:—— 建议重连的延迟,以 ms 为单位。无法通过 JavaScript 进行设置。
event:—— 事件名,必须在data:之前。
一条消息可以按任何顺序包含一个或多个字段,但是
id: 通常排在最后。参考链接:
- 作者:JinSo
- 链接:https://jinso.top/article/modern-javascript-others-network
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。













