Fetch
向服务器发送网络请求,并从服务器获取信息。fetch() 方法是一种现代通用的方法
基本语法:
let promise = fetch(url, [options])
浏览器立即启动请求,并返回一个该调用代码应该用来获取结果的 promise。
获取响应通常需要经过两个阶段。
第一阶段,当服务器发送了响应头(response header),fetch 返回的 promise 就使用内建的 Response class 对象来对响应头进行解析。
第二阶段,为了获取 response body,我们需要使用一个其他的方法调用。
Response 提供了多种基于 promise 的方法,来以不同的格式访问 body:
- response.text() —— 读取 response,并以文本形式返回 response,
- response.json() —— 将 response 解析为 JSON,
- response.formData() —— 以 FormData 对象(在 下一章 有解释)的形式返回 response,
- response.blob() —— 以 Blob(具有类型的二进制数据)形式返回 response,
- response.arrayBuffer() —— 以 ArrayBuffer(低级别的二进制数据)形式返回 response,
- 另外,response.body 是 ReadableStream 对象,它允许你逐块读取 body,我们稍后会用一个例子解释它。
例如,我们从 GitHub 获取最新 commits 的 JSON 对象:
let url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits';
let response = await fetch(url);
let commits = await response.json(); // 读取 response body,并将其解析为 JSON
alert(commits[0].author.login);
Response header
Request header
POST 请求
例如,下面这段代码以 JSON 形式发送 user 对象:
let user = {
name: 'John',
surname: 'Smith'
};
let response = await fetch('/article/fetch/post/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(user)
});
let result = await response.json();
alert(result.message);
总结
典型的 fetch 请求由两个 await 调用组成:
let response = await fetch(url, options); // 解析 response header
let result = await response.json(); // 将 body 读取为 json
或者以 promise 形式:
fetch(url, options)
.then(response => response.json())
.then(result => /* process result */)
Fetch:跨源请求
核心概念是 源(origin)—— 域(domain)/端口(port)/协议(protocol)的组合。
跨源请求 —— 那些发送到其他域(即使是子域)、协议或端口的请求 —— 需要来自远程端的特殊 header。
这个策略被称为 “CORS”:跨源资源共享(Cross-Origin Resource Sharing)。
为什么需要 CORS?跨源请求简史
多年来,来自一个网站的脚本无法访问另一个网站的内容。这个简单有力的规则是互联网安全的基础。
使用表单
<!-- 表单目标 -->
<iframe name="iframe"></iframe>
<!-- 表单可以由 JavaScript 动态生成并提交 -->
<form target="iframe" method="POST" action="http://another.com/…">
...
</form>
使用 script
script 可以具有任何域的 src
如果一个网站,例如 another.com 试图公开这种访问方式的数据,则会使用所谓的 “JSONP (JSON with padding)” 协议。
用于简单请求的 CORS
如果一个请求是跨源的,浏览器始终会向其添加 Origin header。
“非简单”请求
我们可以使用任何 HTTP 方法:不仅仅是 GET/POST,也可以是 PATCH,DELETE 及其他。
XMLHttpRequest
长轮询(Long polling)
长轮询是与服务器保持持久连接的最简单的方式,它不使用任何特定的协议,例如 WebSocket 或者 Server Sent Event。
它很容易实现,在很多场景下也很好用。
常规轮询
从服务器获取新信息的最简单的方式是定期轮询。也就是说,定期向服务器发出请求:“你好,我在这儿,你有关于我的任何信息吗?”例如,每 10 秒一次。
作为响应,服务器首先通知自己,客户端处于在线状态,然后 —— 发送目前为止的消息包。
这可行,但是也有些缺点:
- 消息传递的延迟最多为 10 秒(两个请求之间)。
- 即使没有消息,服务器也会每隔 10 秒被请求轰炸一次,即使用户切换到其他地方或者处于休眠状态,也是如此。就性能而言,这是一个很大的负担。
因此,如果我们讨论的是一个非常小的服务,那么这种方式可能可行,但总的来说,它需要改进。
长轮询
其流程为:
- 请求发送到服务器。
- 服务器在有消息之前不会关闭连接。
- 当消息出现时 —— 服务器将对其请求作出响应。
- 浏览器立即发出一个新的请求。
WebSocket
在 RFC 6455 规范中描述的 WebSocket 协议提供了一种在浏览器和服务器之间建立持久连接来交换数据的方法。数据可以作为“数据包”在两个方向上传递,而不会断开连接和其他 HTTP 请求。
对于需要连续数据交换的服务,例如网络游戏,实时交易系统等,WebSocket 尤其有用。一个简单例子
要打开一个 WebSocket 连接,我们需要在 url 中使用特殊的协议 ws 创建 new WebSocket:
同样也有一个加密的 wss:// 协议。类似于 WebSocket 中的 HTTPS。let socket = new WebSocket("ws://javascript.info");
始终使用 wss://
wss:// 协议不仅是被加密的,而且更可靠。
因为 ws:// 数据不是加密的,对于任何中间人来说其数据都是可见的。并且,旧的代理服务器不了解 WebSocket,它们可能会因为看到“奇怪的” header 而中止连接。
另一方面,wss:// 是基于 TLS 的 WebSocket(类似于 HTTPS 是基于 TLS 的 HTTP),传输安全层在发送方对数据进行了加密,在接收方进行解密。因此,数据包是通过代理加密传输的。它们看不到传输的里面的内容,且会让这些数据通过。
一旦 socket 被建立,我们就应该监听 socket 上的事件。一共有 4 个事件:
- open —— 连接已建立,
- message —— 接收到数据,
- error —— WebSocket 错误,
- close —— 连接已关闭。
如果我们想发送一些东西,那么可以使用 socket.send(data)。
建立 WebSocket
当 new WebSocket(url) 被创建后,它将立即开始连接。
在连接期间,浏览器(使用 header)问服务器:“你支持 WebSocket 吗?”如果服务器回复说“我支持”,那么通信就以 WebSocket 协议继续进行,该协议根本不是 HTTP。
总结
WebSocket 是一种在浏览器和服务器之间建立持久连接的现代方式。
- WebSocket 没有跨源限制。
- 浏览器对 WebSocket 支持很好。
- 可以发送/接收字符串和二进制数据。
WebSocket 方法:
- socket.send(data),
- socket.close([code], [reason])。
WebSocket 事件:
- open,
- message,
- error,
- close。
Server Sent Events
Server-Sent Events 规范描述了一个内建的类 EventSource,它能保持与服务器的连接,并允许从中接收事件。
与 WebSocket 类似,其连接是持久的。
与 WebSocket 相比,EventSource 是与服务器通信的一种不那么强大的方式。
WebSocket | EventSource |
---|---|
双向:客户端和服务端都能交换消息 | 单向:仅服务端能发送消息 |
二进制和文本数据 | 仅文本数据 |
WebSocket 协议 | 常规 HTTP 协议 |
我们需要从服务器接收一个数据流:可能是聊天消息或者市场价格等。这正是 EventSource 所擅长的。它还支持自动重新连接,而在 WebSocket 中这个功能需要我们手动实现。此外,它是一个普通的旧的 HTTP,不是一个新协议。