JavaScript 基础
Document
运算符
深入数据和类型
函数进阶
原型、继承
类
浏览器存储
Web API
事件
错误处理
异步编程
网络请求
模块
练习
实例
工具与规范
软件架构模式
设计模式
Fetch
前端开发的很大一部分工作需要与服务器进行交互,无论任何交互都是发起一个网络请求。在 JavaScript 中提供了网络请求的工具,也就是 fetch 对象。
$$tip fetch 的学习我们以实战为导向讲解,一些冷门的用法与配置不会讲解,想要完整地学习可以参考 Fetch API - MDN。 $$
fetch 的语法:
const promise = fetch(url[, options])
- url:请求的 URL。
- options:可选的参数,详情参考 fetch - MDN。
fetch 返回的是一个 promise 对象。
https://3yya.com/hello.txt 是一个纯文本文件,可以将网址复制到浏览器打开看看,我们试着用 fetch 读取内容并显示:
使用 async、await:
$$jsdemo$$
$$edit$$
async function getData() {
const response = await fetch("https://3yya.com/hello.txt")
const text = await response.text()
console.log(text) // 你好,这里是三眼鸭~
}
getData()
使用 then:
$$jsdemo$$
$$edit$$
const promise = fetch("https://3yya.com/hello.txt")
promise
.then(function (response) {
return response.text()
})
.then(function (text) {
console.log(text) // 你好,这里是三眼鸭~
})
https://3yya.com/hello.json 是一个 JSON 格式的文件,我们试着读取其中的 JSON 内容:
使用 async、await:
$$jsdemo$$
$$edit$$
async function getData() {
const response = await fetch("https://3yya.com/hello.json")
const json = await response.json()
console.log(json) // {name: '三眼鸭', hello: '你好~'}
}
getData()
使用 promise:
$$jsdemo$$
$$edit$$
let promise = fetch("https://3yya.com/hello.json")
promise
.then(function (response) {
return response.json()
})
.then(function (json) {
console.log(json) // {name: '三眼鸭', hello: '你好~'}
})
对于 response 有以下属性:
- status:HTTP 状态码。
- ok:HTTP 状态码为 200 - 299 间为
true
,否则为false
。
并且有以下基于 promise 的方法:
- text:返回文本的内容。
- json:返回经过 JSON 解析的内容。
- formData:返回 FormData 对象。
- blob:返回二进制的数据。
- arrayBuffer:返回 ArrayBuffer 的二进制数据。
携带 json 数据
在发起请求时可以携带 json 数据。
$$tip
get、head
方法不支持携带 json 数据。
Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body. $$
携带 json 数据需要以下几个步骤:
- 设置
method
为非get、head
的方法。 - 表头
headers
中设置Content-Type
为application/json
。 body
中携带序列化后的 json 数据。
比如 https://3yya.com/api/app/test/add
是一个加法的接口。
请求方法: post
参数:
- x:数值。
- y:数值。
返回:
- result:运算结果。
$$jsdemo$$
$$edit$$
async function add(x, y) {
const response = await fetch("https://3yya.com/api/app/test/add", {
method: "POST",
headers: {
"Content-Type": "application/json", // 表明内容是 JSON 格式
},
body: JSON.stringify({
x,
y,
}), // 序列化对象
})
const json = await response.json()
return json.result
}
async function run() {
alert(`5 + 2 = ${await add(5, 2)}`)
}
run()
get 请求携带参数
上面提到, get,head
请求不支持携带 json 数据,那如果有额外的数据要发送给服务器该怎么办?
我们可以将其放到链接的查询参数中,具体参考 URL 章节。
比如 https://3yya.com/api/app/test/add
是一个加法的接口。
请求方法: get
参数:
- x:数值。
- y:数值。
返回:
- result:运算结果。
$$jsdemo$$
$$edit$$
async function add(x, y) {
const url = new URL("https://3yya.com/api/app/test/add")
url.searchParams.append("x", x)
url.searchParams.append("y", y)
const response = await fetch(url)
const json = await response.json()
return json.result
}
async function run() {
alert(`5 + 2 = ${await add(5, 2)}`)
}
run()
实战:使用 POST 方法发布一个帖子
接口文档: 发布帖子
$$jsdemo$$
$$edit$$
async function createPost(content) {
const response = await fetch(
"https://3yya.com/u/d8cf630cf5f367cc/rest/app/posts",
{
method: "post",
headers: {
"Content-Type": "application/json", // 表明内容是 JSON 格式
},
body: JSON.stringify({
content,
}), // 序列化对象
}
)
console.log(response.status) // 200,状态码
}
const content = prompt("请输入你要发表的内容。")
createPost(content)
使用 get
请求通过接口获取我们发布的数据。
$$jsdemo$$
$$edit$$
async function getPosts(content) {
const response = await fetch(
"https://3yya.com/u/d8cf630cf5f367cc/rest/app/posts"
)
return response.json()
}
async function run() {
console.log(await getPosts())
}
run()
练习
- 以下链接中分别有一个数字文本,用
fetch
获取所有结果并求出他们的和,计算下总共的耗时。使用promise
和async、await
两种语法分别实现。
- https://3yya.com/api/app/test/x.txt,响应时间约 3 秒
- https://3yya.com/api/app/test/y.txt,响应时间约 1 秒
- https://3yya.com/api/app/test/z.txt,响应时间约 2 秒
$$answer
promise
语法:
$$jsdemo$$
$$edit$$
function fetchNumber(url) {
return new Promise(function (resolve) {
fetch(url)
.then(function (response) {
return response.text()
})
.then(function (text) {
resolve(Number(text))
})
})
}
const begin = Date.now()
Promise.all([
fetchNumber("https://3yya.com/api/app/test/x.txt"),
fetchNumber("https://3yya.com/api/app/test/y.txt"),
fetchNumber("https://3yya.com/api/app/test/z.txt"),
]).then(function ([x, y, z]) {
console.log(x + y + z) // 35
console.log(`耗时${Date.now() - begin}毫秒`)
})
async
语法:
$$jsdemo$$
$$edit$$
async function fetchNumber(url) {
const response = await fetch(url)
const text = await response.text()
return Number(text)
}
async function run() {
const begin = Date.now()
const [x, y, z] = await Promise.all([
fetchNumber("https://3yya.com/api/app/test/x.txt"),
fetchNumber("https://3yya.com/api/app/test/y.txt"),
fetchNumber("https://3yya.com/api/app/test/z.txt"),
])
console.log(x + y + z) // 35
console.log(`耗时${Date.now() - begin}毫秒`)
}
run()
$$
- 需要下载一个文件的内容并显示,这个文件的下载链接包含在 A 文件当中,而 A 文件的下载链接包含在 B 中, B 又包含在 C 中,C 文件的链接为
https://3yya.com/api/app/test/C.txt
。依次下载 C → B → A → 最终文件。 请使用promise
和async、await
的语法分别实现。
$$answer
promise
语法:
$$jsdemo$$
$$edit$$
function fetchText(url) {
return new Promise(function (resolve) {
fetch(url)
.then(function (response) {
return response.text()
})
.then(function (text) {
resolve(text)
})
})
}
fetchText("https://3yya.com/api/app/test/C.txt")
.then(function (bUrl) {
// 拿到 C 文件的链接
return fetchText(bUrl)
})
.then(function (aUrl) {
// 拿到 B 文件的链接
return fetchText(aUrl)
})
.then(function (finalUrl) {
// 拿到 A 文件的链接
return fetchText(finalUrl)
})
.then(function (text) {
// 拿到最终文件的内容
alert(text) // 恭喜你,得到了最终的文件。
})
async
语法:
$$jsdemo$$
$$edit$$
async function fetchText(url) {
const response = await fetch(url)
return response.text()
}
async function getFinalText() {
// 从 C 文件拿 B 的下载链接
const bUrl = await fetchText("https://3yya.com/api/app/test/C.txt")
// 从 B 文件拿 A 的下载链接
const aUrl = await fetchText(bUrl)
// 从 A 文件拿最终文件的下载链接
const finalUrl = await fetchText(aUrl)
// 得到最终文件的内容
const text = await fetchText(finalUrl)
alert(text)
}
getFinalText()
$$
- 有一个链接
https://3yya.com/api/app/test/0.txt
,其可能返回一个链接或内容。如果返回的是链接则继续加载,如此反复。如果得到内容则显示出来。请使用promise
和async、await
的语法分别实现。
$$tip
可以通过是否以 https://
开头来判断是否是链接。
$$
$$answer
promise
语法:
$$jsdemo$$
$$edit$$
function loadFile(url) {
fetch(url)
.then(function (response) {
return response.text()
})
.then(function (text) {
if (text.startsWith("https://")) {
loadFile(text)
} else {
alert(`最终内容:${text}`)
}
})
}
loadFile("https://3yya.com/api/app/test/0.txt")
async
语法:
$$jsdemo$$
$$edit$$
async function loadFile(url) {
const response = await fetch(url)
const text = await response.text()
if (text.startsWith("https://")) {
loadFile(text)
} else {
alert(`最终内容:${text}`)
}
}
loadFile("https://3yya.com/api/app/test/0.txt")
$$
- 有以下四个接口,计算
5 / 2 + 3 * 4 - 6
的结果,程序中禁止使用加减乘除的运算符。
- https://3yya.com/api/app/test/add ,加。
- https://3yya.com/api/app/test/sub ,减。
- https://3yya.com/api/app/test/mult ,乘。
- https://3yya.com/api/app/test/div ,除。
请求方法: post
参数:
- x:数值。
- y:数值。
返回:
- result:运算结果。
$$answer
$$jsdemo$$
$$edit$$
async function calc(op, x, y) {
const response = await fetch(`https://3yya.com/api/app/test/${op}`, {
method: "POST",
headers: {
"Content-Type": "application/json", // 表明内容是 JSON 格式
},
body: JSON.stringify({
x,
y,
}), // 序列化对象
})
const json = await response.json()
return json.result
}
const add = (x, y) => calc("add", x, y)
const sub = (x, y) => calc("sub", x, y)
const mult = (x, y) => calc("mult", x, y)
const div = (x, y) => calc("div", x, y)
// 计算 5 / 2 + 3 * 4 - 6 的结果
async function run() {
const result1 = await div(5, 2)
const result2 = await mult(3, 4)
const result3 = await add(result1, result2)
const result4 = await sub(result3, 6)
alert(`5 / 2 + 3 * 4 - 6 = ${result4}`)
}
run()
$$