JavaScript 可以向服务器发送网络请求,并在需要时加载新信息。
例如,我们可以使用网络请求来
- 提交订单,
- 加载用户信息,
- 接收来自服务器的最新更新,
- …等等。
…而且所有这些都不需要重新加载页面!
从 JavaScript 发起网络请求有一个总称“AJAX”(缩写为Asynchronous JavaScript And XML)。虽然我们不必使用 XML:这个术语来自旧时代,所以这个词还在那里。你可能已经听说过这个术语了。
有多种方法可以发送网络请求并从服务器获取信息。
fetch()
方法是现代且通用的,所以我们将从它开始。它不受旧浏览器的支持(可以进行 polyfill),但在现代浏览器中得到了很好的支持。
基本语法是
let promise = fetch(url, [options])
url
– 要访问的 URL。options
– 可选参数:方法、标题等。
如果没有 options
,这是一个简单的 GET 请求,下载 url
的内容。
浏览器会立即启动请求并返回一个 promise,调用代码应该使用它来获取结果。
获取响应通常是一个两阶段过程。
首先,fetch
返回的 promise
在服务器响应标题后立即解析为内置 Response 类的对象。
在这个阶段,我们可以检查 HTTP 状态,以查看它是否成功,检查标题,但还没有主体。
如果 fetch
无法进行 HTTP 请求,例如网络问题或没有这样的站点,则 promise 会被拒绝。异常的 HTTP 状态,例如 404 或 500 不会导致错误。
我们可以在响应属性中看到 HTTP 状态
status
– HTTP 状态码,例如 200。ok
– 布尔值,如果 HTTP 状态码为 200-299,则为true
。
例如
let response = await fetch(url);
if (response.ok) { // if HTTP-status is 200-299
// get the response body (the method explained below)
let json = await response.json();
} else {
alert("HTTP-Error: " + response.status);
}
其次,要获取响应主体,我们需要使用额外的函数调用。
Response
提供了多种基于 promise 的方法来以各种格式访问主体
response.text()
– 读取响应并以文本形式返回,response.json()
– 将响应解析为 JSON,response.formData()
– 将响应作为FormData
对象返回(在 下一章 中解释),response.blob()
– 将响应作为 Blob 返回(带有类型的二进制数据),response.arrayBuffer()
– 将响应作为 ArrayBuffer 返回(二进制数据的底层表示),- 此外,
response.body
是一个 ReadableStream 对象,它允许你逐块读取主体,我们将在后面看到一个示例。
例如,让我们从 GitHub 获取一个包含最新提交的 JSON 对象
let url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits';
let response = await fetch(url);
let commits = await response.json(); // read response body and parse as JSON
alert(commits[0].author.login);
或者,使用纯 Promise 语法,不使用 `await` 来实现相同的功能。
fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits')
.then(response => response.json())
.then(commits => alert(commits[0].author.login));
要获取响应文本,使用 `await response.text()` 而不是 `response.json()`。
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
let text = await response.text(); // read response body as text
alert(text.slice(0, 80) + '...');
为了展示二进制格式的读取,让我们获取并显示 “fetch” 规范 的徽标图像(有关 `Blob` 操作的详细信息,请参见 Blob 章)。
let response = await fetch('/article/fetch/logo-fetch.svg');
let blob = await response.blob(); // download as Blob object
// create <img> for it
let img = document.createElement('img');
img.style = 'position:fixed;top:10px;left:10px;width:100px';
document.body.append(img);
// show it
img.src = URL.createObjectURL(blob);
setTimeout(() => { // hide after three seconds
img.remove();
URL.revokeObjectURL(img.src);
}, 3000);
我们只能选择一种主体读取方法。
如果我们已经使用 `response.text()` 获取了响应,那么 `response.json()` 将不起作用,因为主体内容已经处理过了。
let text = await response.text(); // response body consumed
let parsed = await response.json(); // fails (already consumed)
响应头
响应头在 `response.headers` 中的类似 Map 的 headers 对象中可用。
它不完全是 Map,但它具有类似的方法来按名称获取单个头或迭代它们。
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
// get one header
alert(response.headers.get('Content-Type')); // application/json; charset=utf-8
// iterate over all headers
for (let [key, value] of response.headers) {
alert(`${key} = ${value}`);
}
请求头
要在 `fetch` 中设置请求头,我们可以使用 `headers` 选项。它包含一个带有传出头的对象,如下所示。
let response = fetch(protectedUrl, {
headers: {
Authentication: 'secret'
}
});
…但是有一份 禁止的 HTTP 头列表,我们不能设置它们。
Accept-Charset
,Accept-Encoding
Access-Control-Request-Headers
Access-Control-Request-Method
Connection
Content-Length
Cookie
,Cookie2
Date
DNT
Expect
Host
Keep-Alive
Origin
Referer
TE
Trailer
Transfer-Encoding
Upgrade
Via
Proxy-*
Sec-*
这些头确保了正确的和安全的 HTTP,因此它们由浏览器独占控制。
POST 请求
要发出 `POST` 请求或使用其他方法的请求,我们需要使用 `fetch` 选项。
method
– HTTP 方法,例如 `POST`,body
– 请求主体,可以是以下之一:- 字符串(例如 JSON 编码),
FormData
对象,用于以 `multipart/form-data` 形式提交数据,Blob
/BufferSource
用于发送二进制数据,- URLSearchParams,用于以 `x-www-form-urlencoded` 编码提交数据,很少使用。
JSON 格式在大多数情况下使用。
例如,这段代码将 `user` 对象作为 JSON 提交。
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);
请注意,如果请求 `body` 是字符串,则默认情况下 `Content-Type` 头设置为 `text/plain;charset=UTF-8`。
但是,由于我们要发送 JSON,因此我们使用headers
选项发送application/json
,这是 JSON 编码数据的正确Content-Type
。
发送图像
我们还可以使用Blob
或BufferSource
对象通过fetch
提交二进制数据。
在此示例中,有一个<canvas>
,我们可以在其中通过将鼠标悬停在上面来绘制。单击“提交”按钮会将图像发送到服务器
<body style="margin:0">
<canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas>
<input type="button" value="Submit" onclick="submit()">
<script>
canvasElem.onmousemove = function(e) {
let ctx = canvasElem.getContext('2d');
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
};
async function submit() {
let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
let response = await fetch('/article/fetch/post/image', {
method: 'POST',
body: blob
});
// the server responds with confirmation and the image size
let result = await response.json();
alert(result.message);
}
</script>
</body>
请注意,这里我们没有手动设置Content-Type
标头,因为Blob
对象具有内置类型(此处为image/png
,由toBlob
生成)。对于Blob
对象,该类型将成为Content-Type
的值。
submit()
函数可以像这样重写,无需使用async/await
function submit() {
canvasElem.toBlob(function(blob) {
fetch('/article/fetch/post/image', {
method: 'POST',
body: blob
})
.then(response => response.json())
.then(result => alert(JSON.stringify(result, null, 2)))
}, 'image/png');
}
总结
典型的 fetch 请求包含两个await
调用
let response = await fetch(url, options); // resolves with response headers
let result = await response.json(); // read body as json
或者,不使用await
fetch(url, options)
.then(response => response.json())
.then(result => /* process result */)
响应属性
response.status
– 响应的 HTTP 代码,response.ok
– 如果状态为 200-299,则为true
。response.headers
– 包含 HTTP 标头的类似于 Map 的对象。
获取响应主体的方法
response.text()
– 将响应作为文本返回,response.json()
– 将响应解析为 JSON 对象,response.formData()
– 将响应作为FormData
对象返回(multipart/form-data
编码,请参见下一章),response.blob()
– 将响应作为 Blob 返回(带有类型的二进制数据),response.arrayBuffer()
– 将响应作为ArrayBuffer(低级二进制数据)返回,
迄今为止的 Fetch 选项
method
– HTTP 方法,headers
– 包含请求标头的对象(并非所有标头都允许),body
– 要发送的数据(请求主体)作为string
、FormData
、BufferSource
、Blob
或UrlSearchParams
对象。
在接下来的章节中,我们将看到更多fetch
的选项和用例。
评论
<code>
标签,对于多行代码,请将它们包装在<pre>
标签中,对于超过 10 行的代码,请使用沙盒(plnkr,jsbin,codepen…)