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
:
1
px
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
的选项和用例。