本来这篇文章是和 Promise 一起写的,后来学 fetch POST 请求怎么都没理解就单独把这篇文章拿出来自立一篇。传统的 Ajax (小黄人)请求因为设计比较反人类,所以很难用,为了弥(mi 我一直以为是 ni)补,这个不太受待见的东西, fetch 出现了,甚至 fetch 的好用都超过了 JQuery 的封装好的 Ajax 请求。一起来学学。
一、小黄人(XHR)
复习一下小黄人的 GET 和 POST 请求
- get 请求
//垃圾的小黄人
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(this.readyState == 4){
if(this.status ==200 || this.status.toString().indexOf(3) == 0){
const text = this.responseText;
console.log(text);
}
}
}
xhr.open("get","6.txt",true);
xhr.send();
- post 请求
//垃圾的小黄人
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(this.readyState == 4){
if(this.status ==200 || this.status.toString().indexOf(3) == 0){
var a = xhr.responseText;
console.log(a);
}
}
}
xhr.open("post","1.php",true);
// post表格提交必须加上这一条
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xhr.send("name=hero");
//=======================
<?php
$a = $_POST["name"];
echo "成功!";
?>
看见上面代码了吧,是不是很不爽的感觉。一个请求居然要写那么多的内容。小黄人在设计上不符合职责分离原则,将输入、输出和用事件来跟踪的状态混杂在一个对象里。而且,基于事件的模型与 JavaScript 流行的 Promise 以及基于生成器的异步编程模型不太搭,事件模型在处理异步上有点过时了。
轮到 fetch 登场了,先来看看 fetch 的基本语法:
fetch(input, init).then(function(response) { ... });
参数:
- input
定义要获取的资源一般是个 URL 地址。 - init 可选 一个配置项对象,包括所有对请求的设置。可选的参数有:
- method: 请求使用的方法,如 GET、POST,默认GET。
- headers: 请头信息,可以是简单的对象,也可以是 Headers 的实例;
- body: 请求的携带的 body 信息格式是字符串的JSON:类似
'{"name":"Condor Hero","age":18,"sex":"girl"}'或JSON.stringify({"name":"hero","age":18,"sex":"girl"})
- 注意 GET 或 HEAD 方法的请求不能包含 body 信息。
- mode: 请求的模式,如 cors(跨域)、 no-cors:只允许使用 GET 、 HEAD 、 POST ; same-origin:同源请求;
- credentials: 是否发送 cookies,如omit:不发送,默认、same-origin:同源发送;或者 include:发送。
- cache 缓存策略: 请求的 cache 模式: default, no-store, reload, no-cache, force-cache, or only-if-cached.
html5 引进这个新的 Fetch API ,本质是天生返回 Promise 对象,配合 async/await 语法使用。
二、fetch 的默认 GET 请求
来看看最新版的原生 Ajax 请求。一句话搞定了。
fetch("6.txt").then(res => res.text()).then(res => console.log(res)).catch(err => console.log(err));
我们来研究一下第一个 then 里面的返回值,打印出来如下图:
fetch("http://127.0.0.1/2/1.txt").then(res =>console.log(res));
- 如果请求的是 json 数据,需要调用 res.json(),将可读流解析为 json 数据,在下一个then 方法中,就可以得到想要的 json 数据了。同理,如果请求的是 txt 文本数据,则需要调用 res.text() 来解析。
- 第二个 then 里面的 res 用来接收,后台返回的结果。
三、fetch 的 POST 请求
fetch("http://127.0.0.1/2/",{method:"post"}).then(res=>res.json()).then(res=>console.log(res));
必须在http://127.0.0.1/line.html
打开请求,不然提示没有请求头,即使设置:mode:"core"
这个属性是表示服务器设置了core 的才会响应设置这个的 POST 请求。
Post 请求我测试请求路径只能写到文件夹,文件夹里面打开文件的优先级是 index.html →index.php 等其他语言的 index。
2019 年 7 月 19 日也就是昨天没解决的一个问题?
与 php 后台交互,注意看下图的请求方式 Request Payload 不是 Form Data,至于两者的区别参见下方的博客,Request Payload 的请求 PHP 通过
$_POST[]
方法拿不到数据,百度一圈,说可以用print_r(file_get_contents("php://input"));
结果还是拿到了寂寞,我也不是太懂 PHP,有知道的小伙伴欢迎留言。这个问题就留这里了,有机会以后再来解决,谁让 PHP 学的不好。
HTTP请求中的Form Data 与 Request Payload 的区别 - 掘金
var data = {"name":"Condor Hero","age":18,"sex":"girl"};
fetch("./2",{
mode:"cors",
// mode - 是否允许跨域请求,以及哪些响应的属性是可读的。
// 可选值:cors:(默认, 允许跨域请求,将遵守 CORS 协议)
// no-cors:该模式允许来自 CDN 的脚本、其他域的图片和其他一些跨域资源。
// 前提条件是请求的 method 只能是 HEAD、GET 或 POST。而且 js 不能访问 Response 对象中的任何属性
// same-origin:要同源,不允许跨域。
method:"POST",
headers: {
'Content-Type': 'application/json'
},
//json字符串和对象都可以,推荐使用json字符串,这样可以在控制台network里看到请求参数
body: JSON.stringify(data)
// 请求参数
}).then(res =>res.json()).then(res=>console.log(res));
既然我是用本地 PHP 模拟的环境没搞明白,那就用 node.js 或者直接上网趴个接口不就完了,我个沙雕。现在 POST 请求用一个网址返回的内容是一个数组,数组里面是一个大 JSON。
现在来发送一个经典的 POST 请求模板,并得到结果。
fetch("https://jsonplaceholder.typicode.com/users/",{
method : "POST",
headers : {
"Content-Type" : "application/json"
},
body:JSON.stringify({
"name":"Condor Hero",
"age":18,
"sex":"girl"
})
//也可以这样写'{"name":"Condor Hero","age":18,"sex":"girl"}'
}).then(res=>res.json()).then(res=>console.log(res));
看见上图发送 POST 请求的 OPTIONS 了吧,我们接下来揭示一下他的原理。
四、 跨域策略
- JSONP跨域
我们在使用最原始的 Ajax 小黄人请求数据时,受同源策略无法跨域访问文件,不管请求的是什么文件,只要是跨域请求,一律不准。后来发现 script 标签的 src 则不受是否跨域的影响(不仅如此,我们还发现凡是拥有"src"这个属性的标签都拥有跨域的能力,比<img>、<iframe>) 于是诞生了 JOSNP;京东商品评价就是用 JSONP 写的,我们来模拟一下;
<script>
function handleComboCallback(data){
console.log(data);
}
</script>
<script src = "https://c.3.cn/recommend?callback=handleComboCallback&methods=accessories&p=103003&sku=7299682&cat=670%2C677%2C688&lid=1&uuid=1557651031719828760805&pin=&ck=pin%2CipLocation%2Catw%2Caview&lim=5&cuuid=1557651031719828760805&csid=122270672.2.1557651031719828760805%7C4.1563593620&_=1563593637540"></script>
- CORS 跨域
现在我们有一劳永逸新的跨域策略:CORS了。CORS 全称 Cross-Origin Resource Sharing,是 HTML5 规范定义的如何跨域访问资源。Origin 表示本域,也就是浏览器当前页面的域。当J avaScript 向外域(如sina.com)发起请求后,浏览器收到响应后,首先检查Access-Control-Allow-Origin
是否包含本域,如果是,则此次跨域请求成功,如果不是,则请求失败,JavaScript 将无法获取到响应的任何数据。
跨域能否成功,取决于对方服务器是否愿意给你设置一个正确的 Access-Control-Allow-Origin,决定权始终在对方手中。
上面这种跨域请求,称之为“简单请求”。简单请求包括GET 和 POST(主要包括 POST
的 Content-Type 类型中的 application/x-www-form-urlencoded、multipart/form-data和text/plain),并且不能出现任何自定义头(例如,X-Custom: 12345),通常能满足90%的需求。
重点:对于 application/json
的 POST 请求,在发送 AJAX 请求之前,浏览器会先发送一个OPTIONS
请求(称为 preflighted
请求)到这个 URL 上,询问目标服务器是否接受请求。
服务器必须响应并明确指出允许的 Method 请求,
access-control-allow-credentials:true
。所以浏览器必须先确认服务器响应的 Access-Control-Allow-Methods 头确实包含将要发送的 AJAX 请求的 Method,才会继续发送 AJAX,否则,抛出一个错误。
五、fetch 结合async 和 await
Fetch API 是基于 Promise 设计的,所以结合 async 和 await 可以这样写。
async function main(){
var data1 = await fetch("./1.txt").then(data=>data.text());
var data2 = await fetch("./2.txt").then(data=>data.text());
var data3 = await fetch("./3.txt").then(data=>data.text());
console.log(data1);
console.log(data2);
console.log(data3);
}
main();
Fetch 常见坑:
- Fetch 请求默认是不带 cookie 的,需要设置 fetch(url, {credentials: 'include'})
- 服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject.。
附上一个参考博客:Fetch进阶指南