JavaScript 使用 fetch 请求网络

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

在新的 ES7 提案中新添加了 fetch() 方法,fetch() 方法用于发起获取资源的请求。它返回一个 promise,这个 promise 会在请求响应后被 resolve,并传回 Response 对象。
当遇到网络错误时,fetch() 返回的 promise 会被 reject,并传回 TypeError,虽然这也可能因为权限或其它问题导致。成功的 fetch() 检查不仅要包括 promise 被 resolve,还要包括 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 的写法,可以使用 async 和 await 处理 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() 才会继续向下执行,如果 Promise 被 reject() 或抛出异常则会被外面的 try…catch 捕获。

fetch 语法

Promise fetch(input[, init]);

两个参数 input 和 init,input 可以是请求的地址,也可以是一个 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 返回一个 Promise,resolve 时回传 Response 对象。

Response

可以使用 Response.Response() 构造函数来创建一个 Response 对象,但通常会是其他的 API 操作返回了一个 Response 对象,例如一个 service worker 的Fetchevent.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。

参考