Fetch API 提供了能够用于操作一部分 HTTP 管道的 JavaScript 接口,比如 requests 和 responses。它同时也提供了一个全局的 fetch() 方法——能够简单的异步的获取资源
通过检测 Headers、Request、Response 或 fetch() 是否在 Window 或 Worker 域中
if(self.fetch) {
// run my fetch request here
} else {
// do something with XMLHttpRequest?
}
fetch api包含了四个接口,如下:
GlobalFetch包含了一个Fetch方法,而我们发送请求或者获取资源都需要使用这个方法,而GlobalFetch在很多接口中都被实现了,比如 Window 和 WorkerGlobalScope。所以在各种环境中都可以用这个方法获取到资源。
语法
fetch(input, init).then(function(response) { ... });
fetch(URL, {
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!");
});
Headers类允许你去对HTTP request和response headers执行各种操作。 这些操作包括:检索, 设置, 添加和删除。
方法
var myHeaders = new Headers();
myHeaders.append('Content-Type', 'image/jpeg');
myHeaders.get('Content-Type'); //"image/jpeg"
myHeaders.append('Content-Type', 'image/gif');
myHeaders.getAll('Content-Type'); //["image/gif", "image/jpeg"]
...
myHeaders.delete('Content-Type');
myHeaders.getAll('Content-Type'); // []
var myHeaders = new Headers();
myHeaders.append('Content-Type', 'text/xml');
myHeaders.append('Vary', 'Accept-Language');
for (var pair of myHeaders.entries()) {
console.log(pair[0]+ ': '+ pair[1]);
} //content-type: text/xml vary: Accept-Language
var myHeaders = new Headers();
myHeaders.has('Content-Type');//false
myHeaders.append('Content-Type', 'image/jpeg');
myHeaders.has('Content-Type'); //true
var myHeaders = new Headers();
myHeaders.append('Content-Type', 'text/xml');
myHeaders.append('Vary', 'Accept-Language');
for(var key of myHeaders.keys()) {
console.log(key); //content-type vary
}
var myHeaders = new Headers();
myHeaders.append('Content-Type', 'image/jpeg');
myHeaders.set('Content-Type', 'text/html');
myHeaders.get('Content-Type'); //"text/html"
var myHeaders = new Headers();
myHeaders.append('Content-Type', 'text/xml');
myHeaders.append('Vary', 'Accept-Language');
for (var value of myHeaders.values()) {
console.log(value); // text/xml Accept-Language
}
由于Headers可以在request请求中被发送或者在response请求中被接收,并且规定了哪些参数是可写的,Headers对象有一个特殊的guard属性。这个属性没有暴露给Web,但是它影响到哪些内容可以在Headers对象中被改变。 可能的值如下:
相当于一个资源请求
构造器:
var myInit = { method: 'GET',
headers: {
'Content-Type': 'image/jpeg'
},
mode: 'cors',
cache: 'default' };
var myRequest = new Request('flowers.jpg', myInit);
属性
var myRequest = new Request('flowers.jpg');
var myMethod = myRequest.method; // GET
var myRequest = new Request('flowers.jpg');
var myURL = myRequest.url;
var myHeaders = new Headers();
myHeaders.append('Content-Type', 'image/jpeg');
var myInit = { method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default' };
var myRequest = new Request('flowers.jpg',myInit);
myContentType = myRequest.headers.get('Content-Type'); //"image/jpeg"
方法
var res = new Response("one time use");
console.log(res.bodyUsed); // false
res.text().then(function(v) {
console.log(res.bodyUsed); // true
});
console.log(res.bodyUsed); // true
res.text().catch(function(e) {
console.log("Tried to read already consumed Response");
});
这样设计的目的是为了之后兼容基于流的API,让应用一次消费data,这样就允许了JavaScript处理大文件例如视频,并且可以支持实时压缩和编辑。那如果我们需要多次使用body呢?比如我们希望用cache去缓存然后提供离线使用,因此提供了clone()这个方法,调用这个方法可以得到一个克隆对象。不过要记得,clone()必须要在读取之前调用,也就是先clone()再读取。
var res = new Response("one time use");
console.log('res1 ' + res.bodyUsed); // false
var clone = res.clone();
console.log('clone1 ' + clone.bodyUsed); //false
res.text().then(function(v) {
console.log('res ' + res.bodyUsed); // true
});
console.log('resnow ' + res.bodyUsed); // true
console.log('clonenow ' + clone.bodyUsed); //false
clone.text()
.then(function(v){
console.log('clone' + clone.bodyUsed)}) //true
.catch(function(e) {
console.log("Tried to read already consumed Response");
});
无论response还是request都能处理body。我们来看下body。
var form = new FormData(document.getElementById('login-form'));
fetch("/login", {
method: "POST",
body: form
})
呈现了对一次请求的响应数据, Response实例通常在fetch()的回调中获得。但是它们也可以用JS构造,不过通常这招只用于ServiceWorkers。
构造器
var myBlob = new Blob();
var init = { "status" : 200 , "statusText" : "SuperSmashingGreat!" };
var myResponse = new Response(myBlob,init);
属性
方法
var image1 = document.querySelector('.img1');
var image2 = document.querySelector('.img2');
var myRequest = new Request('flowers.jpg');
fetch(myRequest).then(function(response) {
var response2 = response.clone();
response.blob().then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
image1.src = objectURL;
});
response2.blob().then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
image2.src = objectURL;
});
});
有些浏览器(Chrome 45)原生支持 Fetch,但响应中有中文时会乱码,可以使用下面这几行代码来探测是否支持fetch
var userAgent = navigator.userAgent || "";
var appVersion = navigator.appVersion || "";
var vendor = navigator.vendor || "";
var ua = (userAgent + " " + appVersion + " " + vendor).toLowerCase();
var match = /(chrome)[ \/]([\w.]+)/.exec(ua)
|| /(webkit)[ \/]([\w.]+)/.exec(ua)
|| /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua)
|| /(msie) ([\w.]+)/.exec(ua)
|| /(trident)(?:.*? rv:([\w.]+)|)/.exec(ua)
|| ua.indexOf("compatible") < 0
&& /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || [];
var engine = match[0];
var mainVersion = match[2].split(".")[0];
// chrome 内核版本大于 46, firefox 版本大于39 才开启 fetch
if (engine.indexOf("chrome") === 0 && mainVersion >= 46) {
window.__disableNativeFetch = false;
} else if (engine.indexOf("mozilla") === 0 && mainVersion >= 39) {
window.__disableNativeFetch = false;
} else {
window.__disableNativeFetch = true;
}
Fetch 请求默认是不带 cookie 的,需要设置 fetch(url, {credentials: 'include'})
服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject
所有版本的 IE 均不支持原生 Fetch, fetch-ie8 会自动使用 XHR 做 polyfill
API的设计更加简洁
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function() {
console.log(xhr.response);
};
xhr.onerror = function() {
console.log("Booo");
};
xhr.send();
fetch(url).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
}).catch(function() {
console.log("Booo");
});
fetch(url).then(r => r.json())
.then(data => console.log(data))
.catch(e => console.log("Booo"))
(async() => {
try {
var response = await fetch(url);
var data = await response.json();
console.log(data);
} catch (e) {
console.log("Booo")
}
})();
Fetch API足够底层,因为当前的WHATWG标准定义了XMLHttpRequest.send()方法其实等同于fetch的Requset对象。 Fetch中的Response.body实现了getReader()方法用于渐增的读取原始字节流。
function streamingDemo() {
//当数据全部被读完后会将done标记设置为true。 在这种方式下,每次你只需要处理一个chunk,而不是一次性的处理整个响应体。
var req = new Request(URL, {method: 'GET', cache: 'reload'});
fetch(req).then(function(response) {
var reader = response.body.getReader();
return reader.read();
}).then(function(result, done) {
if (!done) {
// do something with each chunk
}
});
}
对于传统的XMLHttpRequest而言,你必须使用它的一个实例来执行请求和检索返回的响应。在这种情况下请求和响应基本没法分开,但是通过Fetch API,我们还能够明确的配置请求对象。Request和Response都完全遵循HTTP标准。因此你可以通过Service Worker API将响应发送给你自己。 Service Worker允许通过截取来自浏览器的请求头和提供本地构造的响应头来替换来自服务器的响应头的方式来构建离线应用。除此之外,还可以使用缓存里的内容。
self.addEventListener('fetch', function(event) {
if (event.request.url === new URL('/', location).href) {
event.respondWith(
new Response("<h1>Hello!</h1>", {
headers: {'Content-Type': 'text/html'}
})
)
}
});
Fetch提供了对'no-cors'的简单处理,当然下面这种情况也无法获取response内部的资源。但这种情况对缓存还是有用的。
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.baidu.com');
xhr.responseType = 'json';
xhr.onload = function() {
console.log('here');
console.log(xhr.response);
};
xhr.onerror = function() {
console.log("Booo");
};
xhr.send();
//XMLHttpRequest cannot load https://www.baidu.com/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://www.zhihu.com' is therefore not allowed access.
// "Booo"
fetch('https://www.baidu.com', {
mode: 'no-cors'
}).then(function(response) {
console.log(response.type); // "opaque"
});
XHR缺少流的处理,在request进行时,你可以得到.responseText,但是整个响应还是在缓存进内存,但是使用Fetch,可以获取底层的body流
Fetch和其他新的API共同使用能发挥更大的作用,比如Cache API,Stream API
Fetch有Header Class,可以修改读取headers,并且提供了ES6的迭代器来操作