fetch api 是一个基于Promise api设计的 xmlHttpRequest 的升级替代品,用于通过javascript发起异步请求。
目前各个主流浏览器的主要版本都内置了Fetch Api;
浏览器支持列表:
既然说fetch是xhr的替代品,那就不得不来说一下他相对xhr来说有哪些优点:
fetch 较于 xhr 来讲,最大的优点在于他是基于Promise的,这样一来你可以方便的进行链式调用,也可以完全利用Promise丰富的api,实现一些复杂的需求。
fetch 遵循模块化设计原则和OOB原则。fetch将一个完整的请求需要的功能按照Http请求的模块划分分散到几个主要的接口上:Request, Response, Header,AbortController, 这样一来,使用起来各部分逻辑关注分离,使得代码更加清晰易懂。而xhr则是将所有的集中在一个接口上。
fetch采用异步数据流处理响应数据,这样一来,数据可以做到按需加载。同xhr必须将数据放入缓存才能使用相比,能大大提高应用的性能。
fetch 同样可以被中止,并且可以被优雅的批量终止。
当然,fetch相比于xhr,也有一个缺点,那就是他不支持进度状态,因此,在一些文件上传下载的业务中,可能不是那么的友好。
fetch api本身只是一个函数,因此的他基础用法很简单:
fetch('http://some.url.com/api').then(response => {
...
})
fetch函数的第一个参数可以是一个资源Url,此时,如果没有第二个参数,将会发起一个简单的get请求。
返回值是一个提供一个Response实例的Promise.
值得一提的是,只要服务端成功响应了本次请求,无论状态码是多少,Promise的状态都是fullfilled状态,只有当遇到网络错误,或者URL无效或者其他原因服务器根本没有响应时,promise的状态才会是rejected.
有必要说一下Response对象常用的的Api:
Response对象时对服务端响应的一个完全封装,response包含一下属性或者方法:
ok属性: 用来标识请求是否成功响应(http状态码在200-299之间)
status属性: 响应的状态码
statusText属性:响应状态信息说明,例如status为200,statusText 为 OK.
type属性: 标识响应的类型, 常用的值有 cors(跨域请求),basic(同源请求), error(网络错误导致的失败的请求)
url属性: 响应对应的URL
Response.useFinalURL属性: 包含了一个布尔值,来标示这是否是该 Response 的最终 URL。
Response.body属性:一个简单的 getter,用于暴露一个 ReadableStream 类型的 body 内容。
Response.bodyUsed属性:包含了一个布尔值 (en-US)来标示该 Response 是否读取过 Body
。
Response.arrayBuffer():读取 Response 对象并且将它设置为已读(因为 Responses 对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为 ArrayBuffer 格式的 Promise 对象。
Response.blob():读取 Response 对象并且将它设置为已读(因为 Responses 对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为 Blob 格式的 Promise 对象。
Body.formData(): 读取Response 对象并且将它设置为已读(因为 Responses 对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为 FormData 格式的 Promise 对象
Body.json(): 读取 Response 对象并且将它设置为已读(因为 Responses 对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为 JSON
格式的 Promise 对象。
Body.text(): 读取 Response 对象并且将它设置为已读(因为 Responses 对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为 USVString 格式的 Promise 对象。
具体使用细节后面会提到。
发起一个复杂的请求有两种方式:
fetch第一个参数为url字符串的时候,可以传入一个配置对象最为请求的各种选项参数,例如
fetch("http://my.service.com/api/path",{ method:'POST', headders:new Headers({ Authorization: '*****' }) })
.then(resp => resp.json(),e => console.error(e))
.then(data => {
console.log(data);
})
options的可选属性列表如下:
method
: 请求使用的方法,如 GET、``POST。
headers
: 请求的头信息,形式为 Headers 的对象或包含 ByteString 值的对象字面量。
body
: 请求的 body 信息:可能是一个 Blob、BufferSource (en-US)、FormData、URLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。
mode
: 请求的模式,如 cors、
no-cors 或者
same-origin。
credentials
: 请求的 credentials,如 omit、``same-origin 或者
include
。为了在当前域名内自动发送 cookie , 必须提供这个选项, 从 Chrome 50 开始, 这个属性也可以接受 FederatedCredential (en-US) 实例或是一个 PasswordCredential (en-US) 实例。
cache
: 请求的 cache 模式: default
、 no-store
、 reload
、 no-cache
、 force-cache
或者 only-if-cached
。
redirect
: 可用的 redirect 模式: follow
(自动重定向), error
(如果产生重定向将自动终止并且抛出一个错误), 或者 manual
(手动处理重定向). 在Chrome中默认使用follow(
Chrome 47之前的默认值是manual
)。
referrer
: 一个 USVString 可以是 no-referrer、``client
或一个 URL。默认是 client。
referrerPolicy
: 指定了HTTP头部referer字段的值。可能为以下值之一: no-referrer、
no-referrer-when-downgrade、
origin、
origin-when-cross-origin、
unsafe-url 。
signal: 终止信号
比较有意思的是signal属性,详细的会在终止fetch部分说明。
fetch也可以直接接受一个Request对象来发起一个复杂的请求,例如上面的例子也可以这样写:
let request = new Request("http://my.service.com/api/path",{ method:'POST', headders:new Headers({ Authorization: '*****' });
fetch(request).then(resp => {
if(resp.ok) {
return resp.json();
} else {
return Promise.reject("error");
}
})
Request 构造函数的第二个参数可选属性实际上和上面fetch第二个参数的可选参数一致。
上面提到了signal属性可以用于中止一个fetch请求,接下来详细说一下。
Fetch终止需要借助另一个接口AbortController, AbortController的使用方法很简单:
let abortController = new AbortController();
AbortController的实例只包含一下两个属性或方法:
abort()方法: 用于中止一个尚未完成的fetch请求操作。
signal: 返回一个AbortSignal对象实例,你可以将signal作为fetch第二个参数的属性或者作为Request第二个参数的属性传递给fetch来控制fetch的状态。
abort调用时,会通知所有使用了这个AbortController实例的signal的fetch请求中止操作。
你可以通过signal.addEventListener('abort',callback) 或者 signal.onabort = callback 的方式来监听abortController的中止事件,当你调用abortController实例的abort()方法时,你的callback函数会被出发,并且获得一个abortEvent;同时如果fetch未完成,那么fetch返回的Promise的状态会变为rejected, reject回调会获得一个名称为AbortError的异常。
例如:
const ac = new AbortController();
const { signal } = ac;
ac.addEventListener('abort',e => console.log('aborted',e))
const req = new Request('http://www/baidu.com',{ signal })
fetch(req).then(resp => resp.text(),e => console.log(e.name))
setTimeout(() => { ac.abort() },1)
//aborted
// Evnet { type:"abort" ... }
// AbortError
const file = document.querySelector("input#file").file[0];
const data = new FormData();
data.append('file', file);
const req = new Request('upload/uri', { method:"POST", body: data })
.then(resp => {
...
})
const headers = { "Content-Type":"application/json" }
const = {
name:'jimi',
age: 18
}
const req = new Request('upload/uri', { method:"POST", headers, body: JSON.stringify(data) })
fetch.then(resp => {
return resp.json()
}).then(data => {
...
})
const req = new Request('https://images2015.cnblogs.com/blog/408483/201702/408483-20170221215434991-1565736785.png');
fetch(req).then(resp => {
return resp.blob()
}).then(img => {
let imgSrc = URL.createObjectURL(img);
let imgNode = document.createElement("img");
imgNode.setAttribute('src', imgSrc);
document.body.appendChild(imgNode);
})
需要特别注意的一点,fetch api返回的response采用流式数据接口,所以一旦数据被读取,你将无法再次读去到数据:例如下面的代码中第二次获取json数据就会报错:
const headers = { "Content-Type":"application/json" }
const = {
name:'jimi',
age: 18
}
const req = new Request('upload/uri', { method:"POST", headers, body: JSON.stringify(data) })
const response = await fetch(req);
const json = await response.json();
const errorJson = await response.json() // Failed to execute 'json' on 'Response': body stream already read
如果确实需要二次读取response的数据流,可以使用response.clone()克隆出一个新的response,数据流读取使用基于被克隆的response对象操作,后续需要再次读取数据,只需要再次克隆一根原来的未被读取的response:
const headers = { "Content-Type":"application/json" }
const = {
name:'jimi',
age: 18
}
const req = new Request('upload/uri', { method:"POST", headers, body: JSON.stringify(data) })
const response = await fetch(req);
const json = await response.clone().json();
const errorJson = await response.clone().json() // 可以正常读取数据