概述
Fetch API
提供了一个 JavaScript 接口,用于访问和操纵HTTP 管道
的一些具体部分,例如请求和响应。它还提供了一个全局 fetch()
方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。
这种功能以前是使用XMLHttpRequest
实现的。Fetch
提供了一个更理想的替代方案,可以很容易地被其他技术使用,例如 Service Workers
。Fetch 还提供了专门的逻辑空间来定义其他与 HTTP 相关的概念,例如 CORS
和 HTTP
的扩展。
请注意,fetch 规范与 jQuery.ajax() 主要有三种方式的不同:
- 当接收到一个代表错误的
HTTP 状态码
时,从fetch()
返回的 Promise不会被标记为 reject
, 即使响应的 HTTP 状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。 - fetch()
不会接受跨域 cookies
;你也不能使用 fetch() 建立起跨域会话。其他网站的 Set-Cookie 头部字段将会被无视。 - fetch
不会发送 cookies
。除非你使用了credentials 的初始化选项。(自 2017 年 8 月 25 日以后,默认的 credentials 政策变更为 same-origin。Firefox 也在 61.0b13 版本中进行了修改)
一个基本的 fetch 请求设置起来很简单。看看下面的代码:
fetch('http://example.com/movies.json')
.then(function(response) {
return response.json(); //由于返回值不是真的json,而是json形式的文本,需要调用json()进行转换
})
.then(function(myJson) {
console.log(myJson);
});
这里我们通过网络获取一个 JSON 文件并将其打印到控制台。最简单的用法是只提供一个参数用来指明想 fetch() 到的资源路径,然后返回一个包含响应结果的promise(一个 Response
对象)。
当然它只是一个HTTP 响应
,而不是真的JSON。为了获取JSON的内容,我们需要使用 json()
方法( json()
方法在 Body mixin
接口中定义, Request 和 Response 对象实现该接口,因此也实现了该方法)。确切来说,此时只是获取了一个Json形式的文本,通过Body.json(),读取 Response 对象并且将它设置为已读(因为 Responses 对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为 JSON 格式的 Promise 对象。
注意:Body mixin 还有其他相似的方法,用于获取其他类型的内容。参考 Body
最好使用符合内容安全策略 (CSP)的链接而不是使用直接指向资源地址的方式来进行Fetch的请求。
支持的请求参数
语法
Promise<Response> fetch(input[, init]);
参数
input
,必选
定义要获取的资源。这可能是:- 一个
USVString 字符串
,包含要获取资源的 URL。一些浏览器会接受 blob: 和 data: 作为 schemes. - 一个
Request
对象。
- 一个
init
,可选
一个配置项对象,包括所有对请求的设置。可选的参数有:
-method
: 请求使用的方法,如GET
、POST
。-
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: 指定了HTTP头部referer字段的值。可能为以下值之一: no-referrer、 no-referrer-when-downgrade、 origin、 origin-when-cross-origin、 unsafe-url 。
-
integrity: 包括请求的 subresource integrity 值 ( 例如: sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=)。
-
fetch() 接受第二个可选参数,一个可以控制不同配置的 init 对象:
参考 fetch(),查看所有可选的配置和更多描述。
POST请求为例:
// Example POST method implementation:
postData('http://example.com/answer', {answer: 42})
.then(data => console.log(data)) // JSON 是经 `response.json()` 转化后的结果
.catch(error => console.error(error))
function postData(url, data) {
// Default options are marked with *
return fetch(url, {
body: JSON.stringify(data), // must match 'Content-Type' header
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, same-origin, *omit
headers: {
'user-agent': 'Mozilla/4.0 MDN Example',
'content-type': 'application/json'
},
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, cors, *same-origin
redirect: 'follow', // manual, *follow, error
referrer: 'no-referrer', // *client, no-referrer
})
.then(response => response.json()) // 转换 response 为 JSON
}
检测请求是否成功
如果是一般故障,比如遇到网络故障,fetch() promise 识别该故障,将会返回 reject(reject表示promise结果是错误的),并带上一个 TypeError 对象。但如果是权限问题或类似问题——比如 404 ,fetch() promise 不识别该故障,会返回resolved (resolved表示promise结果是成功的)。因此想要精确的判断 fetch() 是否成功,需要先判断是否为 promise resolved的情况,此时再判断 Response.ok
是不是为 true。
类似以下代码:
fetch('flowers.jpg').then(function(response) {
if(response.ok) { //需要进一步判断是否成功,避免404场景
return response.blob();
}
throw new Error('Network response was not ok.');
}).then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
}).catch(function(error) {
console.log('There has been a problem with your fetch operation: ', error.message);
});
我们在前文讲述fetch 规范与 jQuery.ajax()区别时,其中有一点就是针对404等错误的处理,
fetch此时返回结果状态是resolve ,但是ok 属性设置为 false
自定义请求对象
除了传给fetch()
一个资源的地址,你还可以通过使用 Request()
构造函数来创建一个request
对象,然后再作为参数传给 fetch():
var myHeaders = new Headers();
var myInit = { method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default' };
var myRequest = new Request('flowers.jpg', myInit);
fetch(myRequest).then(function(response) {
return response.blob();
}).then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
});
Request() 和 fetch() 接受同样的参数。你甚至可以传入一个已存在的 request 对象来创造一个拷贝:
var anotherRequest = new Request(myRequest,myInit);
这个很有用,因为 request 和 response bodies 只能被使用一次(译者注:这里的意思是因为设计成了 stream 的方式,所以它们只能被读取一次)。创建一个拷贝就可以再次使用 request/response 了,当然也可以使用不同的 init 参数。
什么叫只能用一次?待续
注意:clone() 方法也可以用于创建一个拷贝。它和上述方法一样,如果 request 或 response 的 body 已经被读取过,那么将执行失败。区别在于, clone() 出的 body 被读取不会导致原 body 被标记为已读取。
Headers
使用 Headers
的接口,你可以通过 Headers()
构造函数来创建一个你自己的 headers 对象。一个 headers 对象是一个简单的多名值对:
var content = "Hello World";
var myHeaders = new Headers();
myHeaders.append("Content-Type", "text/plain");
myHeaders.append("Content-Length", content.length.toString());
myHeaders.append("X-Custom-Header", "ProcessThisImmediately");
也可以传一个多维数组或者对象字面量:
myHeaders = new Headers({
"Content-Type": "text/plain",
"Content-Length": content.length.toString(),
"X-Custom-Header": "ProcessThisImmediately",
});
它的内容可以被获取:
console.log(myHeaders.has("Content-Type")); // true
console.log(myHeaders.has("Set-Cookie")); // false
myHeaders.set("Content-Type", "text/html");
myHeaders.append("X-Custom-Header", "AnotherValue");
console.log(myHeaders.get("Content-Length")); // 11
console.log(myHeaders.getAll("X-Custom-Header")); // ["ProcessThisImmediately", "AnotherValue"]
myHeaders.delete("X-Custom-Header");
console.log(myHeaders.getAll("X-Custom-Header")); // [ ]
虽然一些操作只能在 ServiceWorkers 中使用,但是它提供了更方便的操作 Headers 的 API。
如果使用了一个不合法的HTTP Header属性名,那么Headers的方法通常都抛出 TypeError 异常。如果不小心写入了一个不可写的属性,也会抛出一个 TypeError 异常。除此以外的情况,失败了并不抛出异常。例如:
var myResponse = Response.error();
try {
myResponse.headers.set("Origin", "http://mybank.com");
} catch(e) {
console.log("Cannot pretend to be a bank!");
}
最好在在使用之前检查内容类型 content-type
是否正确,比如:
判断如果是json类型,才使用json转化的语法response.json()
fetch(myRequest).then(function(response) {
if(response.headers.get("content-type") === "application/json") {
return response.json().then(function(json) {
// process your JSON further
});
} else {
console.log("Oops, we haven't got JSON!");
}
});
Response 对象
如上所述,Response
实例是在fetch()
处理完promise
之后返回的。
你会用到的最常见的 response
属性有:
- Response.status — 整数(默认值为200)为response的状态码。
- Response.statusText — 字符串(默认值为"OK"),该值与 HTTP 状态码消息对应。
- Response.ok — 如上所示,该属性是来检查response的状态是否在
200 - 299
(包括200 和 299)这个范围内。该属性返回一个布尔值。
Response继承了Body,下文的源码中可以看到,因此扩展了json()、blob()等方法。
它的实例也可用通过 JavaScript 来创建,但只有在ServiceWorkers
中才真正有用,当使用 respondWith()
方法并提供了一个自定义的 response 来接受 request 时:
var myBody = new Blob();
addEventListener('fetch', function(event) {
event.respondWith(new Response(myBody, {
headers: { "Content-Type" : "text/plain" }
});
});
Response() 构造方法接受两个可选参数—— response 的数据体和一个初始化对象(与Request() 所接受的 init 参数类似。)
注意: 静态方法 error() 只是返回了错误的response。与此类似地,redirect() 只是返回了一个可以重定向至某 URL 的 response。这些也只与 Service Worker 有关。
源码定义:
//lib.dom.d.ts
interface Body {
readonly bodyUsed: boolean;
arrayBuffer(): Promise<ArrayBuffer>;
blob(): Promise<Blob>;
formData(): Promise<FormData>;
json(): Promise<any>;
text(): Promise<string>;
}
interface Response extends Body { //
readonly body: ReadableStream | null;
readonly headers: Headers;
readonly ok: boolean;
readonly redirected: boolean;
readonly status: number;
readonly statusText: string;
readonly type: ResponseType;
readonly url: string;
clone(): Response;
}
Body
不管是请求还是响应都能够包含 body 对象。body 也可以是以下任意类型的实例。
- ArrayBuffer
- ArrayBufferView (Uint8Array等)
- Blob/File
- string
- URLSearchParams
- FormData
Body 类定义了以下方法(这些方法都被 Request 和Response所实现)以获取 body 内容。这些方法都会返回一个被解析后的Promise对象和数据。
- arrayBuffer()
- blob()
- json()
- text()
- formData()
比起XHR来,这些方法让非文本化的数据使用起来更加简单。
请求体可以由传入 body 参数来进行设置:
var form = new FormData(document.getElementById('login-form'));
fetch("/login", {
method: "POST",
body: form
})
request和response(包括 fetch() 方法)都会试着
自动设置 Content-Type
。如果没有设置 Content-Type 值,发送的请求也会自动设值。