能够执行 XHR 的所有任务,并且能够在 Web 工作者线程等现代 Web 工具中使用,提供拦截,重定向和修改通过 fetch() 生成的请求接口
fetch() 方法的参数与 Request():(RequestInit) 构造器是一样的
declare function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
-
fetch
一定是异步的,天生支持 promise,接收两个参数- 第一个参数:
RequestInfo(request对象)|URL
。URL 是必须的(例如https://www.baidu.com
),只传第一个参数,默认是 get 请求 - 第二个参数是 RequestInit 可选的,是一个对象
method?: string
:请求方法。默认值GET
body?:BodyInit | null
:请求的body
信息。可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象(GET 或者 HEAD 方法的请求不能包含)mode?: RequestMode
:请求的模式(是否使用CORS
)。cors
允许遵守 CORS 的跨源请求(非简单跨源,需要预检)。navigate
、no-cors
(允许不需要发送预检请求的跨源请求,同源请求或者简单跨源)、same-origin
(任何跨源请求都不允许发送)cache?: RequestCache
:请求的 cache 模式:default
,no-store
、reload
、no-cache
、force-cache
或者only-if-cached
credentials?: RequestCredentials
:请求的 credentials,如omit
(不发送 cookie)、same-origin
(同源时发送 cookie)或者include
(无论同源还是跨源都发送)redirect?: RequestRedirect
。可用的 redirect 模式。error
(如果产生重定向将自动终止并且抛出一个错误),follow
(自动重定向,默认值),manual
(手动重定向:fetch 不会跟随跳转,但是resposne.url
会指向新的 url,resposne.redirected
属性会变为true
)referrer?: string
:一个USVString
可以是no-referrer
、client
(默认值)或一个URL。referrerPolicy?: ReferrerPolicy
:指定了 HTTP 标头 referer 字段值的规则。integrity?: string
:请求的哈希值,用于检查 HTTP 响应传回的数据是否为这个预先设定的哈希值,subresource integrity
。keepalive?: boolean
:浏览器是否允许请求存在超出页面生命周期signal?: AbortSignal | null
:用于支持通过AbortController
中断进行中的 fetch() 请求headers?: HeadersInit
- 第一个参数:
-
注意:如果,在浏览器网页中向其他源发起请求,那么必定不是同源,需要使用
mode: "no-cors"
。cors
参数是表示后台必须支持跨源,而 no-cors 一般用于简单请求(图片等静态资源),但是响应会表明你的数据是opaque
,没有访问权限。 -
cache
:default
表示hi现在缓存中寻找匹配;no-store
直接请求远程服务器,并且不更新缓存;reload
直接请求远程服务器,并且更新缓存;no-cache
将服务器资源和本地缓存进行比较,有新的版本才会使用服务器资源;force-cache
缓存优先,在不存在缓存的情况下才会请求远程服务器;only-if-cached
只检查缓存,如果缓存中没有,则返回 504。
-
referrerPolicy
no-referrer-when-downgrade
:默认值,总是发送Referer
标头,除非从 HTTPS 页面请求 HTTP 资源时不发送。no-referrer
:不发送 Referer 标头。origin
:Referer标头只包含域名,不包含完整的路径。origin-when-cross-origin
:同源请求 Referer 标头包含完整的路径,跨源请求只包含域名。same-origin
:跨源请求不发送 Referer,同源请求发送。strict-origin
:Referer 标头只包含域名,HTTPS 页面请求 HTTP 资源时不发送Referer标头。strict-origin-when-cross-origin
:同源请求时Referer标头包含完整路径,跨源请求时只包含域名,HTTPS 页面请求 HTTP 资源时不发送该标头。unsafe-url
:不管什么情况,总是发送 Referer 标头
请求完成时,promise 会兑现为一个
Response
对象
//返回一个response对象
fetch("./README.md").then(response => console.log(response))
读取响应:最简单的方式
text()
fetch("./README.md").then(response => response.text()).then(
text =>console.log(text)
)
处理状态码和请求失败
status
:状态码,例如200
、404
等等statusText
:状态文本。例如OK
、Not Found
等等
- 其中
body
支 持的类型是BodyInit
、BodyInit
为ReadableStream
或者XMLHttpRequestBodyInit
- XMLHttpRequestBodyInit 支持的类型
Blob
、BufferSource
、FormData
、URLSearchParams
、string
-
发送
json
数据const paylod = JSON.stringify({foo:"bar"}) const headers = {"Content-type":"application/json"} fetch("/login",{ method:"POST", body:paylod, headers })
-
在请求体中支持任意字符串值,只需要将上述请求头改成如下所示
const headers = { "Content-type":"application/x-www-form-urlencoded;charset=UTF-8" }
-
发送文件
const imageFormData = new FormData() const imageInput = document.querySelector("input[type='file']") imageFormDate.append("image",imageInput.files[0]) fetch("/imgFile",{ method: "POST", body: imageFormData })
-
加载
blob
文件fetch("./0.jpg").then(response => response.blob()).then( blob => { const src = URL.createObjectURL(blob) const img = new Image() img.src = src document.body.appendChild(img) } )
-
跨源,需要包含
CORS
头保证浏览器收到响应- 服务端设置的响应头
Access-Control-Allow-Origin:<origin> | *
表示允许的来源Access-Control-Allow-Methods:<method>[, <method>]*
表示允许的请求方法Access-Control-Allow-Headers:<header-name>[, <header-name>]*
表示允许的请求头Access-Control-Allow-Credentials: true
表示是否允许发送 Cookie。如果不包含应该去除,而不是写 false- 如果是
XMLHttpRequest
,需要将其withCredentials
标志设置为 true;如果是fetch
,需要设置credentials:include
,表明无论是同源或者跨源都会发送 cookie - 此时
Access-Control-Allow-Origin
不能使用*
,而应该是当前请求的源
- 如果是
- 服务端设置的响应头
-
中断请求:
fetch API
可以通过AbortController/AbortSignal
对请求中断AbortController.abort()
会中断所有网络请求,适合希望停止传输大型负载的情况
const abortController = new AbortController() fetch("ajax.zip",{signal:abortController.signal}) //10ms后中止 setTimeout(()=> abortController.abort(),10)
Headers 对象是发送请求和入站响应头部的容器。并且都可以通过
Request.prototype.headers
修改属性
-
Headers 的类型
HeadersInit
(string[][] | Record<string, string> | Headers),可以是几乎所有的键值对 -
Headers 和 Map 极其相似。都有
set()
、has()
、delete()
、get()
、append()
、entries()
、keys()
、values()
方法
let h = new Headers()
h.set("foo","bar")
console.log(h.has("foo"))//true
let r= new Request(url,init)
与之前的 fetch 是一模一样的,其中 init 和
fetch
中的RequestInit
是一样的。如果 init 中没有设置的值,会使用默认值克隆 Request 对象:构造函数或者
clone()
- 使用构造函数第一个请求体会被标记为已使用
let r = new Request("http://www.baidu.com")
//第一种:如果传入init对象值会覆盖源对象中同名的值
let r1 = new Request(r,{method:"POST"})
console.log(r.bodyUsed)//true
console.log(r1.bodyUsed)//fasle
- 使用
clone()
不会将任何请求体标记为已使用
let r = new Request("http://www.baidu.com",{method:"POST"})
let r2 = r.clone()
console.log(r.bodyUsed)//false
console.log(r2.bodyUsed)//fasle
在 fetch 中使用 request 对象:与 clone() 方法一样,fetch() 不能用使用过的 Request 对象来发送请求
let r = new Request("http://www.baidu.com",{method:"POST"})
//第一种情况
r.text()
fetch(r)//TypeError
//第二种情况
fetch(r)
fetch(r)//TypeError
- 并且 fetch() 的
RequestInit
同样可以覆盖 Request 的对象
let r = new Request("http://www.baidu.com",{method:"POST"})
fetch(r.clone())
fetch(r,{method:"POST",body:"body"})
Request原型上的属性和方法(Request.prototype)
(只读属性)或者方法 | 描述 |
---|---|
cache: RequestCache | RequestInit |
credentials: RequestCredentials; | RequestInit |
headers: Headers; | RequestInit |
integrity: string; | RequestInit |
keepalive: boolean; | RequestInit |
method: string; | RequestInit |
mode: RequestMode; | RequestInit |
redirect: RequestRedirect; | RequestInit |
referrer: string; | RequestInit |
referrerPolicy: ReferrerPolicy; | RequestInit |
signal: AbortSignal; | RequestInit |
url: string; | 路径 |
destination: RequestDestination; | 返回一个描述被请求内容类型的字符串 |
clone(): Request; | 深拷贝request |
Request 和 Response 对象都继承了 Body对象的属性和方法
- 两个只读属性
body
:添加到请求体的内容(实现为ReadableStream
)bodyUsed
:布尔值。请求体中的内容是否已经被读取
- 读取流的方法,返回的都是
promise
- arrayBuffer():Promise<ArrayBuffer>
- blob():Promise<Blob>;
- formData():Promise<FormData>;
- json():Promise<any>;
- text():Promise<string>;
const obj = {hello: 'world'};
const request = new Request('/myEndpoint', {
method: 'POST',
body: JSON.stringify(obj)
});
request.json().then(function(data) {
// do something with the data sent in the request
});
- 使用 streams API 的主要原因是有效载荷的大小可能会导致网络延迟,另一方面是 StreamsAPI 本身处理有效载荷方面是有优势的。
- 除了以上的五个方法之外还有一些注意点
-
一次性流,Body 混入是构建在
ReadableStream
之上的,所以主体流只能使用一次fetch("https://foo.com").then( response=>{ response.blob()//第一次调用会加锁 response.blob()//第二次再调用会出错 //可以使用bodyUsed测试是否加锁 console.log(response.bodyUsed)//true } )
-
使用
ReadableStream
主体,js 编程逻辑很多时候都会将访问网络作为原子操作,比如响应式同时创建和发送的,响应数据是以统一的格式一次性暴露出来的。- 从TCP/IP的角度,传输的数据是以分块的形式抵达端点的。而且速度受到网速的限制。接收端点会为此分配内存,并将收到的块写入内存。Fetch API 通过 ReadableStream 支持这些块到达时就实时读取和操作这些数据
ReadableStream
接口暴露了getReader()
方法,用于产生ReadableStreamDefaultReader
,这个 reader 可以在数据到达时异步获取数据块
fetch("https://fetch.spec.whatwg.org").then( res=>res.body ).then( async function(body){ let reader = body.getReader() while (true) { let {value,done} = await reader.read() if (done) break console.log(value) } } )
产生
Response
对象主要方式是调用fetch()
,他会返回一个 promise,这个Response
对象代表实际 HTTP 的响应
fetch('https://foo.com').then(
response=>{
console.log(response)
}
)
- 初始化
Response
对象- body:(...)
- bodyUsed:false
- headers:Headers {}
- ok:true
- redirected:false
- status:200
- statusText:"OK"
- type:"basic"
- url:"
https://foo.com
"
type
是一种响应的类型。它可能是以下某种值:
basic
: 标准值,同源响应,暴露出了“Set-Cookie”之外的所有标头。cors
:从有效的跨源请求接收到响应。某些标头和主体可以被访问。error
:网络错误。响应的状态为 0,标头为空且不可变。这是从Response.error()
中获得的响应的类型。- error 不会暴露给脚本,它会直接被 Promise 给拒绝
opaque
:对跨源资源的“no-cors”请求的响应。严格限制。opaqueredirect
:fetch 请求是通过redirect: "manual"
发出的。响应的状态是 0,标头是空的,主体是 null,trailer 是空的。
new Response()
new Response(body)
new Response(body, options)
- 其中 body 为响应体,它可以是
string
、URLSearchParams
、Blob
、ArrayBuffer
、TypedArray
、DataView
、FormData
、ReadableStream
其中任意一个 - options 是一个对象:它包含
status
(状态,例如 200)、statusText
(状态文本,例如 ok)、headers
(头部信息)
如果想要克隆 Response 对象,可以使用
Request
对象的 clone() 方法
const res = new Response(...)
res.clone()
- 同时
Response
对象也继承Body
对象,拥有Body
属性所有的对象和方法
Response 类有两个静态方法
Response.error()
和Response.redirect()
- Response.redirect():接收一个 url 和重定向状态码,返回重定向 Response 对象
- 提供的状态码必须对应重定向,反则抛出错误
Response.redirect("https://foo.com",301)
实现下载进度,递归的读取
reader
- 这里需要使用
ReadableStream.tee()
来拷贝流,这样可以同时使用流获取下载进度和使用流读取数据
const progress = (res) => {
const total = res.headers.get("content-length")
let count = 0
const [progressStream, dataStream] = res.body.tee()
const reader = progressStream.getReader()
const log = (reader) => {
reader.read().then(({ value, done }) => {
if (done) return console.log("done")
count += value.length
console.log(`Downloaded ${count} of ${total} (${(count / total * 100).toFixed(2)}%)`)
return log(reader)
})
}
log(reader)
return new Response(dataStream, { headers: res.headers })
}
fetch("README.md").then(
progress
).then(
res => res.text()
).then(
data => console.log(data)
)
- 同时我们也可以自己构造一个 readableStream 去读取流
fetch('test.md')
.then((res) => {
const total = res.headers.get("content-length")
let count = 0
const reader = res.body.getReader();
return new ReadableStream({
start(controller) {
return pump();
function pump() {
return reader.read().then(({ done, value }) => {
if (done) {
controller.close();
return;
}
console.log(queue.size(value))
controller.enqueue(value);
console.log(`Download ${count += value.length} of ${total} ${(count / total * 100).toFixed(2)}%`)
return pump();
}, queue);
}
}
})
});
const queue = new ByteLengthQueuingStrategy({
highWaterMark: 1
})
- 使用
ReadableStream.cancel()
中断请求,并且丢弃所有数据
-
直接使用
ReadableStream.cancel()
取消源流- 这时候
cancel
报一个错误。由于res.text()
读取流时,reader 已经锁定到该流,不能取消已经锁定的流 - Failed to execute 'cancel' on 'ReadableStream': Cannot cancel a locked stream
let aborter = null; const abortHandler = (res) => { // `res.body`不能再次获取流 aborter = () => res.body.cancel(); return res; }; fetch("README.txt").then(abortHandler).then(res => { res.text() aborter() }).then(data => console.log(data)).catch(err => console.log(err))
- 这时候
-
使用
ReadableStreamDefaultReader.cancel()
取消源流- 这时候
cancel
报一个错误:由于已经使用res.text()
锁定读取流,同一个流不能继续再加锁 - Failed to execute 'getReader' on 'ReadableStream': ReadableStreamDefaultReader constructor can only accept readable streams that are not yet locked to a reader
let aborter = null; const abortHandler = (res) => { // `res.body.getReader()`不能再次获取 reader 读取器 aborter = () => res.body.getReader().cancel(); return res; }; fetch("README.txt").then(abortHandler).then(res => { res.text() aborter() }).then(data => console.log(data)).catch(err => console.log(err))
- 这时候
-
只有重新构造一个
ReadableStream
let aborter = null; const abortHandler = (res) => { const reader = res.body.getReader() const stream = new ReadableStream({ start(controller) { let aborted = false const push = () => { reader.read().then(({ value, done }) => { if (done) { if (!aborted) controller.close() return } controller.enqueue(value) push() }) } aborter = () => { aborted = true reader.cancel() controller.error(new Error("中止 aborted")) } push() } }) return new Response(stream, { headers: res.headers }) }; fetch("README.txt").then(abortHandler).then(res => { res.text() aborter() }).then(data => console.log(data)).catch(err => console.log(err))
此事件类型为
fetch
,并且只在 service worker 全局作用域中。构造函数的形式一般不常用。
- 比较重要的属性是
request
,此属性是一个Request
对象,并且该属性不能为空,如果是构造函数,需要初始化它的参数对象。
respondWith(Response|Promise(Response)):此方法会阻止浏览器的默认响应,而由自己提供
参考:https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith
waitUntil(promise):该方法会通知浏览器传入的 promise 任务一直在进行中,仍然没有完成
参考:https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil