JavaScript 使用 fetch 请求网络

Fetch API 提供了一个获取资源的接口(包括跨网络)。任何使用过 XMLHttpRequest 的人都会熟悉它,新的 API 提供了更强大和更灵活的功能集。Fetch 提供了对 RequestResponse (以及其他与网络请求有关的)对象的通用定义。使之今后可以被使用到更多地应用场景中:无论是 service workersCache API、又或者是其他处理请求和响应的方式,甚至是任何一种需要你自己在程序中生成响应的方式。

在新的 ES7 提案中新添加了 fetch() 方法,fetch() 方法用于发起获取资源的请求。它返回一个 promise,这个 promise 会在请求响应后被 resolve,并传回 Response 对象。
当遇到网络错误时,fetch() 返回的 promise 会被 reject,并传回 TypeError,虽然这也可能因为权限或其它问题导致。成功的 fetch() 检查不仅要包括 promiseresolve,还要包括 Response.ok 属性为 true。HTTP 404 状态并不被认为是网络错误。
此功能某些浏览器尚在开发中,所以暂时不能直接在生产环境使用,现阶段浏览器端开发,可以使用 isomorphic-fetch 替代。

传统 XMLHttpRequest 请求网络

1
2
3
4
5
6
7
8
9
10
11
12
13
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function() {
console.log(xhr.response);
};
xhr.onerror = function() {
console.log("Oops, error");
};
xhr.send();

上面是一个典型的 XMLHttpRequest 请求发起流程。

使用 fetch 请求网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const fetch = require('isomorphic-fetch');
fetch("https://api.github.com/emojis")
.then((response) => {
if(response.status == 200){
return response.json();
}
})
.then((emojis)=> {
var keyLength = 0;
for(var prop in emojis) {
keyLength++;
var url = emojis[prop];
}
console.log(keyLength);
})
.catch((e)=> {
console.log(e);
});

注:需要提前安装 isomorphic-fetch。

上面的代码还可以进一步简写成:

1
2
3
4
5
6
7
8
fetch("https://api.github.com/emojis")
.then(response => response.json())
.then(json=>onGetJson(json))
.catch(e=>console.log(e));
function onGetJson(json) {
// TODO
}

async & await

上面的写法流程看起来更加清晰,但是仍然是 Callback 的写法,可以使用 asyncawait 处理 fetch ,让整个流程就像是同步流程一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fetch = require('isomorphic-fetch');
(async ()=> {
try {
var data = await fetch("https://api.github.com/emojis");
var json = await data.json();
onGetEmojis(json);
} catch(e) {
console.log(e);
}
})()
function onGetEmojis(json) {
// TODO
}

使用 await 后,写异步代码就像写同步代码一样。await 后面可以跟 Promise 对象,表示等待 Promise resolve() 才会继续向下执行,如果 Promisereject() 或抛出异常则会被外面的 try…catch 捕获。

fetch 语法

Promise fetch(input[, init]);

两个参数 inputinitinput 可以是请求的地址,也可以是一个 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: 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 返回一个 Promiseresolve 时回传 Response 对象。

Response

可以使用 Response.Response() 构造函数来创建一个 Response 对象,但通常会是其他的 API 操作返回了一个 Response 对象,例如一个 service workerFetchevent.respondWith,或者一个简单的 GlobalFetch.fetch()

constructor

Response.Response() // 创建一个 Response 对象

attr & method

  • Response.ok 包含了一个布尔值来标示该Response成功(状态码200-299) 还是失败
  • Body.json() 读取 Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为JSON格式的promise对象
  • Body.text() 读取 Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为USVString格式的promise对象

版本兼容

fetch 原生支持率不高,幸运的是,引入下面这些 polyfill 后可以完美支持 IE8+ :

  • 由于 IE8 是 ES3,需要引入 ES5 的 polyfill: es5-shim, es5-sham
  • 引入 Promise 的 polyfill: es6-promise
  • 引入 fetch 探测库:fetch-detector
  • 引入 fetch 的 polyfill: fetch-ie8
  • 可选:如果你还使用了 jsonp,引入 fetch-jsonp
  • 可选:开启 Babel 的 runtime 模式,现在就使用 async/await

Fetch polyfill 的基本原理是探测是否存在 window.fetch 方法,如果没有则用 XHR 实现。这也是 github/fetch 的做法,但是有些浏览器(Chrome 45)原生支持 Fetch,但响应中有中文时会乱码,老外又不太关心这种问题,所以我才有了 fetch-detector 和 fetch-ie8 只在浏览器稳定支持 Fetch 情况下才使用原生 Fetch。

参考