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-Typeapplication/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()

练习

  1. 以下链接中分别有一个数字文本,用 fetch 获取所有结果并求出他们的和,计算下总共的耗时。使用 promiseasync、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()

$$

  1. 需要下载一个文件的内容并显示,这个文件的下载链接包含在 A 文件当中,而 A 文件的下载链接包含在 B 中, B 又包含在 C 中,C 文件的链接为 https://3yya.com/api/app/test/C.txt。依次下载 C → B → A → 最终文件。 请使用 promiseasync、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()

$$

  1. 有一个链接 https://3yya.com/api/app/test/0.txt,其可能返回一个链接或内容。如果返回的是链接则继续加载,如此反复。如果得到内容则显示出来。请使用 promiseasync、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")

$$

  1. 有以下四个接口,计算 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()

$$