目录
1 Fetch API
1.1 基本用法
1.2 fetch()
1.2.1 CORS机制下发出跨域请求
1.2.2 发出POST请求
1.3 Headers对象
1.4 Request对象
1.5 Response对象
1.6 body属性
Ajax操作所用的XMLHttpRequest对象,已经有十多年的历史,它的API设计并不是很好,输入、输出、状态都在同一个接口管理,容易写出非常混乱的代码。Fetch API是一种新规范,用来取代XMLHttpRequest对象。它主要有两个特点,一是简化接口,将API分散在几个不同的对象上,二是返回Promise对象,避免了嵌套的回调函数。
检查浏览器是否部署了这个API的代码如下。
if (fetch in window){
// 支持
} else {
// 不支持
}
下面是一个Fetch API的简单例子。
var URL = 'http://some/path';
fetch(URL).then(function(response) {
return response.json();
}).then(function(json) {
someOperator(json);
});
上面代码向服务器请求JSON文件,获取后再做进一步处理。
下面比较XMLHttpRequest写法与Fetch写法的不同:
function reqListener() {
var data = JSON.parse(this.responseText);
console.log(data);
}
function reqError(err) {
console.log('Fetch Error :-S', err);
}
var oReq = new XMLHttpRequest();
oReq.onload = reqListener;
oReq.onerror = reqError;
oReq.open('get', './api/some.json', true);
oReq.send();
同样的操作用Fetch实现如下。
fetch('./api/some.json')
.then(function(response) {
if (response.status !== 200) {
console.log('请求失败,状态码:' + response.status);
return;
}
response.json().then(function(data) {
console.log(data);
});
}).catch(function(err) {
console.log('出错:', err);
});
上面代码中,因为HTTP请求返回的response对象是一个Stream对象,所以需要使用response.json
方法转为JSON格式,不过这个方法返回的是一个Promise对象。
fetch方法的第一个参数可以是URL字符串,也可以是后文要讲到的Request对象实例。Fetch方法返回一个Promise对象,并将一个response对象传给回调函数。
response对象还有一个ok属性,如果返回的状态码在200到299之间(即请求成功),这个属性为true,否则为false。因此,上面的代码可以写成下面这样。
fetch("./api/some.json").then(function(response) {
if (response.ok) {
response.json().then(function(data) {
console.log(data);
});
} else {
console.log("请求失败,状态码为", response.status);
}
}, function(err) {
console.log("出错:", err);
});
response对象除了json方法,还包含了HTTP回应的元数据。
fetch('users.json').then(function(response) {
console.log(response.headers.get('Content-Type'));
console.log(response.headers.get('Date'));
console.log(response.status);
console.log(response.statusText);
console.log(response.type);
console.log(response.url);
});
上面代码中,response对象有很多属性,其中的response.type
属性比较特别,表示HTTP回应的类型,它有以下三个值。
如果需要在CORS机制下发出跨域请求,需要指明状态。
fetch('http://some-site.com/cors-enabled/some.json', {mode: 'cors'})
.then(function(response) {
return response.text();
})
.then(function(text) {
console.log('Request successful', text);
})
.catch(function(error) {
log('Request failed', error)
});
除了指定模式,fetch方法的第二个参数还可以用来配置其他值,比如指定cookie连同HTTP请求一起发出。
fetch(url, {
credentials: 'include'
})
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) {
console.log("Perfect! Your settings are saved.");
} else if (res.status == 401) {
console.log("Oops! You are not authorized.");
}
}, function(e) {
console.log("Error submitting form!");
});
目前,还有一些XMLHttpRequest对象可以做到,但是Fetch API还没做到的地方,比如中途中断HTTP请求,以及获取HTTP请求的进度。这些不足与Fetch返回的是Promise对象有关。
Fetch API引入三个新的对象(也是构造函数):Headers, Request 和 Response。其中,Headers对象用来构造/读取HTTP数据包的头信息。
var 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");
Headers对象的实例,除了使用append方法添加属性,也可以直接通过构造函数一次性生成。
reqHeaders = new Headers({
"Content-Type": "text/plain",
"Content-Length": content.length.toString(),
"X-Custom-Header": "ProcessThisImmediately",
});
Headers对象实例还提供了一些工具方法。
reqHeaders.has("Content-Type") // true
reqHeaders.has("Set-Cookie") // false
reqHeaders.set("Content-Type", "text/html")
reqHeaders.append("X-Custom-Header", "AnotherValue")
reqHeaders.get("Content-Length") // 11
reqHeaders.getAll("X-Custom-Header") // ["ProcessThisImmediately", "AnotherValue"]
reqHeaders.delete("X-Custom-Header")
reqHeaders.getAll("X-Custom-Header") // []
生成Header实例以后,可以将它作为第二个参数,传入Request方法。
var headers = new Headers();
headers.append('Accept', 'application/json');
var request = new Request(URL, {headers: headers});
fetch(request).then(function(response) {
console.log(response.headers);
});
同样地,Headers实例可以用来构造Response方法。
var headers = new Headers({
'Content-Type': 'application/json',
'Cache-Control': 'max-age=3600'
});
var response = new Response(
JSON.stringify({photos: {photo: []}}),
{'status': 200, headers: headers}
);
response.json().then(function(json) {
insertPhotos(json);
});
上面代码中,构造了一个HTTP回应。目前,浏览器构造HTTP回应没有太大用处,但是随着Service Worker的部署,不久浏览器就可以向Service Worker发出HTTP回应。
Request对象用来构造HTTP请求。
var req = new Request("/index.html");
req.method // "GET"
req.url // "http://example.com/index.html"
Request对象的第二个参数,表示配置对象。
var uploadReq = new Request("/uploadImage", {
method: "POST",
headers: {
"Content-Type": "image/png",
},
body: "image data"
});
上面代码指定Request对象使用POST方法发出,并指定HTTP头信息和信息体。
下面是另一个例子。
var req = new Request(URL, {method: 'GET', cache: 'reload'});
fetch(req).then(function(response) {
return response.json();
}).then(function(json) {
someOperator(json);
});
上面代码中,指定请求方法为GET,并且要求浏览器不得缓存response。
Request对象实例有两个属性是只读的,不能手动设置。一个是referrer属性,表示请求的来源,由浏览器设置,有可能是空字符串。另一个是context属性,表示请求发出的上下文,如果是image,表示是从img标签发出,如果是worker,表示是从worker脚本发出,如果是fetch,表示是从fetch函数发出的。
Request对象实例的mode属性,用来设置是否跨域,合法的值有以下三种:same-origin、no-cors(默认值)、cors。当设置为same-origin时,只能向同域的URL发出请求,否则会报错。
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!");
});
上面代码中,如果用户输入的URL不是同域的,将会报错,否则就会发出请求。
如果mode属性为no-cors,就与默认的浏览器行为没有不同,类似script标签加载外部脚本文件、img标签加载外部图片。如果mode属性为cors,就可以向部署了CORS机制的服务器,发出跨域请求。
var u = new URLSearchParams();
u.append('method', 'flickr.interestingness.getList');
u.append('api_key', '');
u.append('format', 'json');
u.append('nojsoncallback', '1');
var apiCall = fetch('https://api.flickr.com/services/rest?' + u);
apiCall.then(function(response) {
return response.json().then(function(json) {
// photo is a list of photos.
return json.photos.photo;
});
}).then(function(photos) {
photos.forEach(function(photo) {
console.log(photo.title);
});
});
上面代码是向Flickr API发出图片请求的例子。
Request对象的一个很有用的功能,是在其他Request实例的基础上,生成新的Request实例。
var postReq = new Request(req, {method: 'POST'});
fetch方法返回Response对象实例,它有以下属性。
Response对象还有两个静态方法。
Request对象和Response对象都有body属性,表示请求的内容。body属性可能是以下的数据类型。
var form = new FormData(document.getElementById('login-form'));
fetch("/login", {
method: "POST",
body: form
})
上面代码中,Request对象的body属性为表单数据。
Request对象和Response对象都提供以下方法,用来读取body。
注意,上面这些方法都只能使用一次,第二次使用就会报错,也就是说,body属性只能读取一次。Request对象和Response对象都有bodyUsed属性,返回一个布尔值,表示body是否被读取过。
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");
});
上面代码中,第二次通过text方法读取Response对象实例的body时,就会报错。
这是因为body属性是一个stream对象,数据只能单向传送一次。这样的设计是为了允许JavaScript处理视频、音频这样的大型文件。
如果希望多次使用body属性,可以使用Response对象和Request对象的clone方法。它必须在body还没有读取前调用,返回一个前的body,也就是说,需要使用几次body,就要调用几次clone方法。
addEventListener('fetch', function(evt) {
var sheep = new Response("Dolly");
console.log(sheep.bodyUsed); // false
var clone = sheep.clone();
console.log(clone.bodyUsed); // false
clone.text();
console.log(sheep.bodyUsed); // false
console.log(clone.bodyUsed); // true
evt.respondWith(cache.add(sheep.clone()).then(function(e) {
return sheep;
});
});