谈谈 fetch 以及使用方法

1,342 阅读4分钟

谈谈 fetch 以及使用方法

最近写项目接触到了 fetch,之前对于 fetch 也仅仅是停留在知道有这么东西,并没有去过多的接触了解。所以这次写代码碰到,也刚好抽时间了解学习一下。

fetch 可以说是 XMLHttpReques 的替代方案。在我们的开发日常中,我们除了使用 ajax 去调用接口获取后台数据,还可以通过使用 axiosfetch 来替代 ajax

axios 就不在本文过多的述说了,相信小伙伴们对 axios 也是有一定的了解的,本文主要是讲解一下 fetch 以及它的使用方式。

什么是 fetch?

Fetch被称为下一代 Ajax 技术,采用 Promise 的方式来处理数据。它是一种简洁明了的API,比 XMLHttpRequest 更加简单易用。

在我们页面中需要向服务器请求数据时,基本上都会使用 Ajax 来实现。Ajax 的本质是使用XMLHttpRequest 对象来请求数据,而 XMLHttpRequest 对象是通过事件的模式来实现返回数据的处理。

与 XMLHttpRequest 类似,Fetch 允许你发出 AJAX 请求。

区别在于Fetch API使用Promise方式,Promise是已经正式发布的ES6的内容之一,因此是一种简洁明了的API,比 XMLHttpRequest 更加简单易用。

基本使用

接下来书写一段简单的代码,先简单的了解熟悉一下 fetch 是怎么使用的。

说明:

  1. fetch 的返回值是一个 promise 对象
  2. fetch 接受一个 URL 字符串,默认向该网址发送 get 请求
  3. fetch 处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用

它的基本使用如下:

fetch(url)
  .then((response)=>{
      console.log(response)
  }).catch((err)=>{
      console.log(err)
  })

下面写一个简单的例子:

fetch(url)
  .then((response)=>{
      response.json()
  })
  .then((json)=>{
      console.log(json)
  })
  .catch((err)=>{
      console.log(err)
  })

参数

参数:

  • url:请求地址路径 (必写)
  • options:选择项,是个对象
    • method: 请求方式,get/post,不写默认 get
    • body:请求参数,body 是必写的,所以一般会用到 JSON.stringify
    • headers: HTTP请求头

接下来写一个完整的:

const getList = (url,params)=>{
    fetch(url,{
        method: 'get',
        body: JSON.stringify(params),
        headers: {
            'Content-Type': 'application/json;charset=UTF-8'
        }
    }).then(response=>{
        response.json()
    }).catch(err=>{
        console.log(err)
    })
}

当然,这么写看起来是有些繁琐,刚才我们讲过了,fetch 的返回值是一个 promise,所以我们可以合理使用 async、await 来精简我们的代码。

const getList = async(url,params)=>{
let request = {
        method: 'get',
        body: JSON.stringify(params),
        headers: {
            'Content-Type': 'application/json;charset=UTF-8'
        }
    }
    try{
        const response = await fetch(url,request)
    }catch(err){
        console.log(err)
    }
}

上面写的例子,可以看到我们写的 await 是写在 try、catch 里面的,这样才能捕获到错误异常。

我们直接打印 response 是长这样的:

image.png

可以看到里面有很多东西,那我们应该怎么获取到我们想要的东西呢?

response 对象

response 包含的数据通过 Stream 接口异步读取,但是它还包含一些同步属性,对应 HTTP 回应的标头信息(Headers),可以立即读取。

同步属性

比如: response.status 和 response.statusText 就是 response 的同步属性

标头属性有下面这些:

1.response.ok:

返回的是一个布尔值,表示请求是否成功。true 对应的状态码是 200-299,false 对应其他的状态码。

2.response.status: 返回一个数字,表示 http 回应的状态码,200 表示成功。

3.response.statusText:

返回一个字符串,表示 http 回应的状态信息。(例如成功,服务器返回 'OK' )

4.response.url:

返回请求的 URL路径。如果 URL 存在跳转,则返回的是最终的 URL。

5.response.type:

返回请求的类型。可能存在的值如下:

basic:普通请求,即同源请求。
cors:跨域请求。
error:网络错误。
opaque:如果 fetch请求 的 type 设置成 'no-cors',就会返回这个值。 
opaqueredirect: 如果 fetch请求 的 redirect 设置为 'manual',会返回这个值

6. response.redirected:

返回一个布尔值,表示请求是否跳转过。

判断请求是否成功

既然这里都讲到了回应状态,就顺便讲一下,应该怎么判断请求是否成功。

fetch() 发出请求以后,有一个很重要的注意点:只有网络错误,或者无法连接时,fetch() 才会报错,其他情况都不会报错,而是认为请求成功。

也就是说,即使服务器返回的状态码是 4xx 或 5xx,fetch() 也不会报错(即 Promise 不会变为 rejected状态)。

只有通过 Response.status 属性,得到 HTTP 回应的真实状态码,才能判断请求是否成功。

async function fetchText() {
    let response = await fetch(url)
    if (response.status >= 200 && response.status < 300) {
        return await response.text()
    } else {
        throw new Error(response.statusText)
    }
}

这里不用考虑网址跳转(状态码为 3xx),因为 fetch() 会将跳转的状态码自动转为 200。

也可以使用 response.ok() 来判断:

if (response.ok) {
    // 请求成功
} else {
    // 请求失败
}

Headers 属性

response 还有一个属性:response.headers。指向一个标头,对应该请求的所有标头。

Headers 对象可以使用 for...of...进行遍历。

Headers 对象提供了以下几个方法来操作标头。

1. headers.get():

根据指定的键名,获取键值。

2. headers.has():

返回一个布尔值,表示是否包含某个标头。

3. headers.set():

将指定的键名设置为新的键值,如果该键名不存在则会添加。

4. headers.append():

添加标头。

5. headers.delete():

标头删除。

6. headers.keys():

返回一个遍历器,可以依次遍历所有键名。

7. headers.values():

返回一个遍历器,可以依次遍历所有键值。

8. headers.entries():

返回一个遍历器,可以依次遍历所有键值对(key, value)。

9. headers.forEach():

依次遍历标头,每个标头都会执行一次参数函数。

上面的有些方法可以修改标头,那是因为继承于 Headers 接口。但是对于 HTTP 回应来说,修改标头意义不大,况且很多标头是只读的,浏览器不允许修改。

这些方法中,最常用的是 response.headers.get() ,用于读取某个标头的值。

例如:

let response =  await  fetch(url)
const type = response.headers.get('Content-Type')
console.log(type)
// application/json; charset=utf-8

读取内容

response 对象根据服务器返回的不同类型,提供了不同的读取方式

有一点需要注意的是:

这几个读取方法都是异步的,返回的都是 Promise 对象必须等到异步操作结束,才能得到服务器返回的完整数据。

response.text():得到文本字符串。
response.json():得到 JSON 对象。
response.blob():得到二进制 Blob 对象。
response.formData():得到 FormData 表单对象。
response.arrayBuffer():得到二进制 ArrayBuffer 对象。

1. response.text():

读取 response 对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次) ,并返回一个被解析为 USVString 格式的 promise 对象

可以用于获取文本数据,比如 HTML 文件。

2. response.json():

读取 response 对象并且将它设置为已读,并返回一个被解析为 JSON 格式的 promise 对象

主要用于获取服务器返回的 JSON 数据

3. response.blob():

读取 response 对象并且将它设置为已读,并返回一个被解析为 Blob 格式的 promise 对象

用于获取二进制文件。

3. response.formDate():

读取 response 对象并且将它设置为已读,并返回一个被解析为 FormData 格式的 promise 对象

主要用在 Service Worker 里面,拦截用户提交的表单,修改某些数据以后,再提交给服务器。

4. response.arrayBuffer():

读取 response 对象并且将它设置为已读,并返回一个被解析为 ArrayBuffer 格式的promise 对象

主要用于获取流媒体文件。

刚才说了,因为 Responses 对象被设置为了 stream 的方式,而 Stream 对象只能读取一次,读取完就没了。这就意味着上面的五个读取方式只能使用其中一个,否则会报错。

let text =  await response.text()
let json =  await response.json()  // 报错

因为先使用了 response.text(),就把 Stream 读完了。后面再调用 response.json() ,就没有内容可读了,所以报错。

response.clone()

但是如果我们就是想读取多次,该怎么办呢?

别担心,Response 对象提供Response.clone()方法,创建 Response 对象的副本,实现多次读取。

例如:

const response1 = await fetch('flowers.jpg')
const response2 = response1.clone()

const myBlob1 = await response1.blob()
const myBlob2 = await response2.blob()

image1.src = URL.createObjectURL(myBlob1)
image2.src = URL.createObjectURL(myBlob2)

上面的例子里,response.clone()复制了一份 Response 对象,然后将同一张图片读取了两次。

response.body

Response.body 属性是 Response 对象暴露出的底层接口,返回一个 ReadableStream 对象,供用户操作。

它可以用来分块读取内容,应用之一就是显示下载的进度。

例如:

const response = await fetch('flower.jpg')
const reader = response.body.getReader()

while(true) {
  const {done, value} = await reader.read()

  if (done) {
    break
  }

  console.log(`Received ${value.length} bytes`)
}

response.body.getReader()方法返回一个遍历器。这个遍历器的 read() 方法每次返回一个对象,表示本次读取的内容块。

这个对象的 done 属性是一个布尔值,用来判断有没有读完;value 属性是一个 arrayBuffer 数组,表示内容块的内容,而 value.length 属性是当前块的大小。

强制携带Cookie

默认情况下, fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于维护一个用户会话,则导致未经认证的请求(要发送 cookies,必须发送凭据头)。

我们可以通过设置 credentials: 'include', 强制加入凭据头:

fetch(url, {
    method: 'GET',
    credentials: 'include' // 强制加入凭据头
  })
  .then((response)=>{
      return response.text()
  })
  .then((response)=>{
      console.log(response)
  })