fetch-api简介

在抱怨自己赚钱之少之前先努力学着让自己值钱。
十多年来,我们一直使用 XMLHttpRequest(XHR)来发送异步请求,XHR 很实用,但并不是一个设计优良的 API,在设计上并不符合职责分离原则,输入、输出以及状态都杂糅在同一对象中,并用事件机制来跟踪状态变化。并且,基于事件的模型与最近流行的 Promise 和 generator 异步编程模型不太友好。

Fetch API 旨在修正上述缺陷,它提供了与 HTTP 语义相同的 JS 语法,简单来说,它引入了 fetch() 这个实用的方法来获取网络资源。

在 Fetch 规范中对 API 进行了定义,它结合 ServiceWorkers,尝试做到如下优化:

  • 改善离线体验
  • 保持可扩展性

简单示例

Fetch API 中最常用的是 fetch() 方法,该方法最简单的形式是,接受一个 URL 参数并返回以一个 promise 对象:

1
2
3
4
5
6
7
8
9
10
11
12
fetch("/data.json").then(function(res) {
// res instanceof Response == true.
if (res.ok) {
res.json().then(function(data) {
console.log(data.entries);
});
} else {
console.log("Looks like the response wasn't perfect, got status", res.status);
}
}, function(e) {
console.log("Fetch failed!", e);
});

如果是提交一个POST请求,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fetch("http://www.example.org/submit.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: "firstName=Nikhil&favColor=blue&password=easytoguess"
}).then(function(res) {
if (res.ok) {
alert("Perfect! Your settings are saved.");
} else if (res.status == 401) {
alert("Oops! You are not authorized.");
}
}, function(e) {
alert("Error submitting form!");
});

fetch() 方法的参数和 Request() 构造函数的参数完全一致,所以你可以传任意复杂的参数来实现更强大的 fetch(),下面将详细介绍。

Headers

Fetch 引入了 3 个接口,分别是 Headers,Request 和 Response。他们直接对应于的 HTTP 中相应的概念,但是基于隐私和安全考虑,也有些区别,例如支持 CORS 规则以及保证 cookies 不能被第三方获取。

Headers 接口是一个简单的键值对:

1
2
3
4
5
6
ar content = "Hello World";
var reqHeaders = new Headers();
reqHeaders.append("Content-Type", "text/plain"
reqHeaders.append("Content-Length", content.length.toString());
reqHeaders.append("X-Custom-Header", "ProcessThisImmediately");

也可以给构造函数传一个多维数组或 JS 字面量对象:

1
2
3
4
5
reqHeaders = new Headers({
"Content-Type": "text/plain",
"Content-Length": content.length.toString(),
"X-Custom-Header": "ProcessThisImmediately",
});

Headers 的内容可被检索:

1
2
3
4
5
6
7
8
9
10
console.log(reqHeaders.has("Content-Type")); // true
console.log(reqHeaders.has("Set-Cookie")); // false
reqHeaders.set("Content-Type", "text/html");
reqHeaders.append("X-Custom-Header", "AnotherValue");

console.log(reqHeaders.get("Content-Length")); // 11
console.log(reqHeaders.getAll("X-Custom-Header")); // ["ProcessThisImmediately", "AnotherValue"]

reqHeaders.delete("X-Custom-Header");
console.log(reqHeaders.getAll("X-Custom-Header")); // []

一些操作只在 ServiceWorkers 中可用,但这些 API 使得操作 header 更为方便。

由于 header 可以在发送请求时被发送或在收到响应时被接收,并规定了那些参数可写,所以在 Headers 对象中有个一 guard 属性,来指定哪些参数可以被改变。

可能的值如下:

  • “none”:默认值
  • “request”:Request.headers 对象只读
  • “request-no-cors”:在 no-cors 模式下,Request.headers 对象只读
  • “response”:Response.headers 对象只读
  • “immutable”:通常在 ServiceWorkers 中使用,所有 Header 对象都为只读
    在规范中对每个 guard 属性值有更详细的描述。例如,当 guard 为 request 时,你将不能添加或修改header 的 Content-Length 属性。

如果使用了一个不合法的 HTTP Header 名,那么 Headers 的方法通常都抛出 TypeError 异常。如果不小心写入了一个只读属性,也会抛出一个 TypeError 异常。除此以外,失败了将不抛出任何异常。例如:

1
2
3
4
5
6
var res = Response.error();
try {
res.headers.set("Origin", "http://mybank.com");
} catch(e) {
console.log("Cannot pretend to be a bank!");
}

Request

通过构造一个 Request 对象来获取网络资源,构造函数需要 URL、method 和 headers 参数,同时也可以提供请求体(body)、请求模式(mode)、credentials 和 cache hints 等参数。

最简单的形式如下:

1
2
3
var req = new Request("/index.html");
console.log(req.method); // "GET"
console.log(req.url); // "http://example.com/index.html"

也可以将一个 Request 对象传给构造函数,这将返回该对象的一个副本(这与 clone() 方法不同,后面将介绍)。

1
2
3
var copy = new Request(req);
console.log(copy.method); // "GET"
console.log(copy.url); // "http://example.com/index.html"

同时,这种形式通常只在 ServiceWorkers 中使用。

除 URL 之外的参数只能通过第二个参数传递,该参数是一个键值对:

1
2
3
4
5
6
7
var uploadReq = new Request("/uploadImage", {
method: "POST",
headers: {
"Content-Type": "image/png",
},
body: "image data"
});

mode 参数用来决定是否允许跨域请求,以及哪些 response 属性可读。可选的 mode 值为 “same-origin”、”no-cors”(默认)以及 “cors”。

same-origin

该模式很简单,如果一个请求是跨域的,那么将返回一个 error,这样确保所有的请求遵守同源策略。

1
2
3
4
5
6
var arbitraryUrl = document.getElementById("url-input").value;
fetch(arbitraryUrl, { mode: "same-origin" }).then(function(res) {
console.log("Response succeeded?", res.ok);
}, function(e) {
console.log("Please enter a same-origin URL!");
});

Response

Response 对象通常在 fetch() 的回调中获得,也可以通过 JS 构造,不过这通常只在 ServiceWorkers 中使用。

Response 对象中最常见的属性是 status(整数,默认值是 200)和statusText(默认值是 “OK”)。还有一个 ok 属性,这是 status 值为 200~299 时的语法糖。

另外,还有一个 type 属性,它的值可能是 “basic”、”cors”、”default”、”error” 或 “opaque”。

  • “basic”:同域的响应,除 Set-Cookie 和 Set-Cookie2 之外的所有 Header 可用
  • “cors”:Response 从一个合法的跨域请求获得,某些 Header 和 body 可读
  • “error”:网络错误。Response 对象的 status 属性为 0,headers 属性为空并且不可写。当 Response 对象从 Response.error() 中得到时,就是这种类型
  • “opaque”:在 “no-cors” 模式下请求了跨域资源。依靠服务端来做限制
    当 type 属性值为 “error” 时会导致 fetch() 方法的 Promise 被 reject,reject 回调的参数为 TypeError 对象。

还有一些属性只在 ServerWorker 下有效。在 ServerWorker 下返回一个 Response 的正确方式为:

1
2
3
4
5
addEventListener('fetch', function(event) {
event.respondWith(new Response("Response body", {
headers: { "Content-Type" : "text/plain" }
});
});

如你所见,Response 构造函数接收两个参数:返回的 body 和一个键值对对象,通过该对象来设置 status、statusText 和 headers 属性。

静态方法 Response.error() 将返回一个错误响应,Response.redirect(url, status) 将返回一个跳转响应。

http://bubkoo.com/2015/05/08/introduction-to-fetch/
https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch