PWA系列——Fetch API

时间:2022-06-19
本文章向大家介绍PWA系列——Fetch API,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

PWA系列——Fetch API

今天聊聊 xhr 的替代品 Fetch,在全局作用域中有个 fetch 方法方便使用。虽然同样也是处理 HTTP 请求和响应的,但 fetch 有两个不同之处,一个是收到错误的 HTTP 状态码时,fetch 方法返回的 Promise 不会被 reject,而是将 resolve 的对象中名为 ok 属性设置为 false,只有在网络出现故障的情况下才会被 reject。另外一个不同之处就是默认不会发送和接收 cookies,如果需要那么必须手动设置 credentials

使用 Fetch 我们需要了解 fetch、Request、Response、Headers 以及 Body,这几个都是使用 Fetch API 所需要了解的。

fetch

作为全局作用域中的 fetch,首先我们需要快速了解一下 fetch 方法如何调用(参考 MDN):

  • 他可以接收一个 USVString 字符串或者一个 Request 对象(下文会讲到 Request 对象)
  • 以及一个可选的配置参数(配置参数包括一系列对请求的设置可选的参数有):
  • method: 请求使用的方法
  • headers: 请求的头信息,形式为 Headers 的对象 或包含 ByteString 值的对象字面量
  • body: 请求的 body 信息:可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象 。注意 GET 或 HEAD 方法的请求不能包含 body 信息。
  • mode: 请求的模式,如 cors、 no-cors 或者 same-origin
  • credentials: 请求的 credentials,如 omit、same-origin 或者 include。为了在当前域名内自动发送 cookie ,必须提供这个选项,从 Chrome 50 开始, 这个属性也可以接受 FederatedCredential 实例或是一个 PasswordCredential 实例。
  • cache: 请求的 cache 模式: default 、 no-store 、 reload 、 no-cache 、 force-cache 或者 only-if-cached
  • redirect: 可用的 redirect 模式: follow (自动重定向), error (如果产生重定向将自动终止并且抛出一个错误), 或者 manual (手动处理重定向). 在Chrome中,Chrome 47之前的默认值是 follow,从 Chrome 47开始是 manual。
  • referrer: 一个 USVString 可以是 no-referrer、client或一个 URL。默认是 client。
  • referrerPolicy: Specifies the value of the referer HTTP header. May be one of no-referrer、 no-referrer-when-downgrade、 origin、 origin-when-cross-origin、 unsafe-url 。
  • integrity: 包括请求的 subresource integrity 值 ( 例如: sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=)。

看个例子:

通过 fetch 发送 png 图片请求,并使用 blob 方法和 createObjectUrl 方法将数据转为 Object URL,并通过 img 元素显示出来:

+(async function () {
+    // 创建 url
+    let url = new URL('https://httpbin.org/image/png')
+})()

首先创建一个 url 实例

; (async function () {
    // 创建 url
    let url = new URL('https://httpbin.org/image/png')
    // 创建 header 头
+   let header = new Headers({
+       'X-Custom-Header': 'custom header value'
+   })
})()

创建实例 header 头,接收自定义请求头信息

(async function () {
    // 创建 url
    let url = new URL('https://httpbin.org/image/png')
    // 创建 header 头
    let header = new Headers({
        'X-Custom-Header': 'custom header value'
    })
+   // 创建 request 请求
+   let request = new Request(url, {
+       method: 'GET',
+       headers: header
+   })
})()

创建 request 请求,将 url header 等必要信息传入到 Request 构造函数

(async function () {
    // 创建 url
    let url = new URL('https://httpbin.org/image/png')
    // 创建 header 头
    let header = new Headers({
        'X-Custom-Header': 'custom header value'
    })
    // 创建 request 请求
    let request = new Request(url, {
        method: 'GET',
        headers: header
    })
+   // 接收 resposne 响应
+   let response = await fetch(request)
+   let blobData = await response.blob() // 接收 blob 对象
})()

最后通过 fetch 并将 request 作为参数传入发起请求,并通过 response 响应对象的 blob 方法接收到图片数据

(async function () {
    // 创建 url
    let url = new URL('https://httpbin.org/image/png')
    // 创建 header 头
    let header = new Headers({
        'X-Custom-Header': 'custom header value'
    })
    // 创建 request 请求
    let request = new Request(url, {
        method: 'GET',
        headers: header
    })
    // 接收 resposne 响应
    let response = await fetch(request)
    let blobData = await response.blob() // 接收 blob 对象

+   let objUrl = URL.createObjectURL(blobData) // 转为 Object URL

+   let img = new Image()
+   img.src = objUrl
+   document.body.appendChild(img)
})()

最后调用 URL 静态方法 createObjectURL 将 blob 对象转为 ObjectURL 创建 img 并追加到 body 中

上述涉及到了 Request、Response、Headers 构造函数,我们再来看看这三个构造函数分别接收什么参数以及包括什么属性和方法吧

Request 请求

Request 构造函数接收的参数跟 fetch 方法一样,URL 链接、Request 对象、以及一系列可选参数如:

(async function () {
    let myRequest = new Request('./demo.jpeg', {
        method: 'GET',
        headers: {
            'Content-Type': 'image/jpeg'
        },
        mode: 'cors',
        cache: 'default'
    })
})()

Request 实例

同时他也支持传入另一个 Request 实例,通过构造器可创建一个请求的副本。

(async function () {
    let myRequest = new Request('./demo.jpeg', {
        method: 'GET',
        headers: {
            'Content-Type': 'image/jpeg'
        },
        mode: 'cors',
        cache: 'default'
    })
+   let myRequest1 = new Request(myRequest)
})()

Request 实例有一系列只读属性,如 method 返回请求方法、url 返回请求地址、headers 返回 Headers 对象等。有兴趣童鞋可以打印出来研究研究。?

另外,Request 实现了 Body 的一系列方法,如 blob 获取请求实体的 blob,json 方法返回 body 的 json 数据

  • Body.arrayBuffer() 返回解决一个ArrayBuffer表示的请求主体的promise.
  • Body.blob() 返回解决一个Blob表示的请求主体的promise.
  • Body.formData() 返回解决一个FormData表示的请求主体的promise.
  • Body.json() 返回解决一个JSON表示的请求主体的promise.
  • Body.text() 返回解决一个USVString(文本)表示的请求主体的promise.

比如:

(async function () {
    let myRequest = new Request('./demo', {
        method: 'POST',
        mode: 'cors',
        cache: 'default',
        body: JSON.stringify({ name: 'oli' })
    })

    let data = await myRequest.json() // 获取到的 data 为 parsed json 数据
})()

同样的上述 Body 在 Response 示例中也有实现。

Response 响应

通过 new 操作符创建 Reponse 的实例,Response 实例并非一定需要发送真正的请求才可获得。通过构造函数我们可以自己去构造一个 Response 实例 ?:

Response 构造函数接收两个可选参数,一个是 Body 实例,一个是参数对象,两个都是可选的:

(async function () {
+   let myRes = new Response()
+   console.log(myRes)
})()

如果不穿入任何参数,默认 body 为 null,bodyUsed 为 false,ok 为 true,status 为 200.

我们尝试传入参数,让他返回一个 blob 并自定义 statusText 和 Headers:

(async function () {
    let blob = new Blob([JSON.stringify({ name: 'oli' })], { type: 'application/json' })

    let opts = {
        statusText: 'not modified',
        headers: new Headers({
            'X-Custom-Header': 'hey there'
        })
    }

    let myRes = new Response(blob, opts)
    console.log(myRes.headers.get('X-Custom-Header'))
    console.log(await myRes.blob())
})()

Response 实例

Response 实例也是有一系列只读属性,如 status 返回状态码,ok 表示 Response 是否成功,另外还有 statusText 等,可以打印出来研究一下。 ?

如果有 body 数据,那么与上文中提到的 Request 一样包含五个方法用来解析数据

Headers 请求或响应头

Headers 构造函数用来创建请求或响应头的。他接收一个对象作为参数,这个对象代表的就是一系列 header:

let httpHeaders = { 'Content-Type' : 'image/jpeg' }
let myHeaders = new Headers(httpHeaders)

Headers 实例还有 append 方法用来追加 header,或 get 方法获取 header 对应的值,除此之外,还有 delete、entries等:

  • Headers.append() 给现有的header添加一个值, 或者添加一个未存在的header并赋值.
  • Headers.delete() 从Headers对象中删除指定header.
  • Headers.entries() 以 迭代器 的形式返回Headers对象中所有的键值对.
  • Headers.get() 以 ByteString 的形式从Headers对象中返回指定header的全部值.
  • Headers.has() 以布尔值的形式从Headers对象中返回是否存在指定的header.
  • Headers.keys() 以迭代器的形式返回Headers对象中所有存在的header名.
  • Headers.set() 替换现有的header的值, 或者添加一个未存在的header并赋值.
  • Headers.values() 以迭代器的形式返回Headers对象中所有存在的header的值.

Body 请求实体

最后还要提到一个 Body,它含有一个 bodyUsed 属性,只读的并返回 body 是否被读取过:

(async function () {
    let blob = new Blob([JSON.stringify({ name: 'oli' })], { type: 'application/json' })
    let opts = {
        statusText: 'not modified',
        headers: new Headers({
            'X-Custom-Header': 'hey there'
        })
    }
    let myRes = new Response(blob, opts)

    console.log(myRes.bodyUsed) // false ❌
    let data = myRes.blob()
    console.log(myRes.bodyUsed) // true ✅
})()

结合 Cache API 代码段

结合上篇文章介绍的 Cache API,我们尝试使用 Fetch 获取请求数据并保存缓存,然后每次刷新检测是否存在缓存,存在即获取缓存的数据:

(async function () {
    // 定义 request 实例 ?
    let request = new Request('https://httpbin.org/image/png')

    // append 方法 ?
    function appendImg(blobData) { // 这里定义一个 append 方法将 blob 数据传入创建 img 标签显示图片
        let urlObj = URL.createObjectURL(blobData)
        let img = new Image()
        img.src = urlObj
        document.body.appendChild(img)
    }

    // 创建 cache 对象 ?
    let cacheName = 'DEMO_CACHE_V1'
    let cache = await caches.open(cacheName)

    // 检查 request 请求是否命中缓存 ?
    let matchedResponse = await cache.match(request)
    if (matchedResponse) {
        console.log('matched')
        let blobData = await matchedResponse.blob()
        // 命中缓存则获取缓存的响应实例 ?
        appendImg(blobData)
    } else {
        // 没有命中缓存则使用 fetch 发起请求并使用 Cache API 缓存 ?
        let response = await fetch(request)
        await cache.put(request, response)
        response = await cache.match(request)
        let blobData = await response.blob()
        appendImg(blobData)
    }
})()

这就是 Fetch 的基本内容。同样的我们在开发 PWA 应用的时候会面临大量用户数据需要存储在客户端的情况。这个时候就需要用到 indexedDB 了,这部分内容较多,一时半会说不完,改天有时间梳理一下。 ?