Fetch是什么?Fetch与Axios、Ajax
XMLHttpRequest 是一个设计粗糙的 API,不符合关注分离(Separation of Concerns)的原则,配置和调用方式非常混乱,而且基于事件的异步模型写起来也没有现代的 Promise,generator/yield,async/await 友好。Fetch 的出现就是为了解决 XHR 的问题。Fetch、Ajax、Axios区别。
Why Fetch
XMLHttpRequest 是一个设计粗糙的 API,不符合关注分离(Separation of Concerns)的原则,配置和调用方式非常混乱,而且基于事件的异步模型写起来也没有现代的 Promise,generator/yield,async/await 友好。
Fetch 的出现就是为了解决 XHR 的问题,拿例子说明:
使用 XHR 发送一个 json 请求一般是这样:
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();
使用 Fetch 后,顿时看起来好一点
fetch(url).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
}).catch(function(e) {
console.log("Oops, error");
});
使用 ES6 的 箭头函数 后:
fetch(url).then(response => response.json())
.then(data => console.log(data))
.catch(e => console.log("Oops, error", e))
现在看起来好很多了,但这种 Promise 的写法还是有 Callback 的影子,而且 promise 使用 catch 方法来进行错误处理的方式有点奇怪。不用急,下面使用 async/await 来做最终优化:
注:async/await 是非常新的 API,属于 ES7,目前尚在 Stage 1(提议) 阶段,这是它的完整规范。使用 Babel 开启 runtime 模式后可以把 async/await 无痛编译成 ES5 代码。也可以直接使用 regenerator 来编译到 ES5。
try {
let response = await fetch(url);
let data = response.json();
console.log(data);
} catch(e) {
console.log("Oops, error", e);
}
// 注:这段代码如果想运行,外面需要包一个 async function
duang~~ 的一声,使用 await
后,写异步代码就像写同步代码一样爽。await
后面可以跟 Promise 对象,表示等待 Promise resolve()
才会继续向下执行,如果 Promise 被 reject()
或抛出异常则会被外面的 try...catch
捕获。
Promise,generator/yield,await/async 都是现在和未来 JS 解决异步的标准做法,可以完美搭配使用。这也是使用标准 Promise 一大好处。最近也把项目中使用第三方 Promise 库的代码全部转成标准 Promise,为以后全面使用 async/await 做准备。
另外,Fetch 也很适合做现在流行的同构应用,有人基于 Fetch 的语法,在 Node 端基于 http 库实现了 node-fetch,又有人封装了用于同构应用的 isomorphic-fetch。
注:同构(isomorphic/universal)就是使前后端运行同一套代码的意思,后端一般是指 NodeJS 环境。
总结一下,Fetch 优点主要有:
- 语法简洁,更加语义化
- 基于标准 Promise 实现,支持 async/await
- 同构方便,使用 isomorphic-fetch
fetch和XMLHttpRequest
如果看网上的fetch教程,会首先对比XMLHttpRequest和fetch的优劣,然后引出一堆看了很快会忘记的内容(本人记性不好)。因此,我写一篇关于fetch的文章,为了自己看着方便,毕竟工作中用到的也就是一些很基础的点而已。
fetch,说白了,就是XMLHttpRequest的一种替代方案。如果有人问你,除了Ajax获取后台数据之外,还有没有其他的替代方案?
这是你就可以回答,除了XMLHttpRequest对象来获取后台的数据之外,还可以使用一种更优的解决方案fetch。
如何获取fetch
到现在为止,fetch的支持性还不是很好,但是在谷歌浏览器中已经支持了fetch。fetch挂在在BOM中,可以直接在谷歌浏览器中使用。
查看fetch的支持情况:fetch的支持情况
当然,如果不支持fetch也没有问题,可以使用第三方的ployfill来实现只会fetch:whatwg-fetch
fetch的helloworld
下面我们来写第一个fetch获取后端数据的例子:
// 通过fetch获取百度的错误提示页面
fetch('https://www.baidu.com/search/error.html') // 返回一个Promise对象
.then((res)=>{
return res.text() // res.text()是一个Promise对象
})
.then((res)=>{
console.log(res) // res是最终的结果
})
是不是很简单?可能难的地方就是Promise的写法,这个可以看阮一峰老师的ES6教程来学习。
说明一点,下面演示的GET请求或POST请求,都是采用百度中查询到的一些接口,可能传递的有些参数这个接口并不会解析,但不会影响这个接口的使用。
GET请求
GET请求初步
完成了helloworld,这个时候就要来认识一下GET请求如何处理了。
上面的helloworld中这是使用了第一个参数,其实fetch还可以提供第二个参数,就是用来传递一些初始化的信息。
这里如果要特别指明是GET请求,就要写成下面的形式:
// 通过fetch获取百度的错误提示页面
fetch('https://www.baidu.com/search/error.html', {
method: 'GET'
})
.then((res)=>{
return res.text()
})
.then((res)=>{
console.log(res)
})
GET请求的参数传递
GET请求中如果需要传递参数怎么办?这个时候,只能把参数写在URL上来进行传递了。
// 通过fetch获取百度的错误提示页面
fetch('https://www.baidu.com/search/error.html?a=1&b=2', { // 在URL中写上传递的参数
method: 'GET'
})
.then((res)=>{
return res.text()
})
.then((res)=>{
console.log(res)
})
POST请求
POST请求初步
与GET请求类似,POST请求的指定也是在fetch的第二个参数中:
// 通过fetch获取百度的错误提示页面
fetch('https://www.baidu.com/search/error.html', {
method: 'POST' // 指定是POST请求
})
.then((res)=>{
return res.text()
})
.then((res)=>{
console.log(res)
})
POST请求参数的传递
众所周知,POST请求的参数,一定不能放在URL中,这样做的目的是防止信息泄露。
// 通过fetch获取百度的错误提示页面
fetch('https://www.baidu.com/search/error.html', {
method: 'POST',
body: new URLSearchParams([["foo", 1],["bar", 2]]).toString() // 这里是请求对象
})
.then((res)=>{
return res.text()
})
.then((res)=>{
console.log(res)
})
其实除了对象URLSearchParams
外,还有几个其他的对象,可以参照:常用的几个对象来学习使用。
设置请求的头信息
在POST提交的过程中,一般是表单提交,可是,经过查询,发现默认的提交方式是:Content-Type:text/plain;charset=UTF-8
,这个显然是不合理的。下面咱们学习一下,指定头信息:
// 通过fetch获取百度的错误提示页面
fetch('https://www.baidu.com/search/error.html', {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded' // 指定提交方式为表单提交
}),
body: new URLSearchParams([["foo", 1],["bar", 2]]).toString()
})
.then((res)=>{
return res.text()
})
.then((res)=>{
console.log(res)
})
这个时候,在谷歌浏览器的Network中查询,会发现,请求方式已经变成了content-type:application/x-www-form-urlencoded
。
通过接口得到JSON数据
上面所有的例子中都是返回一个文本,那么除了文本,有没有其他的数据类型呢?肯定是有的,具体查询地址:Body的类型
由于最常用的是JSON数据,那么下面就简单演示一下获取JSON数据的方式:
// 通过fetch获取百度的错误提示页面
fetch('https://www.baidu.com/rec?platform=wise&ms=1&rset=rcmd&word=123&qid=11327900426705455986&rq=123&from=844b&baiduid=A1D0B88941B30028C375C79CE5AC2E5E%3AFG%3D1&tn=&clientWidth=375&t=1506826017369&r=8255', { // 在URL中写上传递的参数
method: 'GET',
headers: new Headers({
'Accept': 'application/json' // 通过头指定,获取的数据类型是JSON
})
})
.then((res)=>{
return res.json() // 返回一个Promise,可以解析成JSON
})
.then((res)=>{
console.log(res) // 获取JSON数据
})
强制带Cookie
默认情况下, fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于维护一个用户会话,则导致未经认证的请求(要发送 cookies,必须发送凭据头).
// 通过fetch获取百度的错误提示页面
fetch('https://www.baidu.com/search/error.html', {
method: 'GET',
credentials: 'include' // 强制加入凭据头
})
.then((res)=>{
return res.text()
})
.then((res)=>{
console.log(res)
})
简单封装一下fetch
最后了,介绍了一大堆内容,有没有发现,在GET和POST传递参数的方式不同呢?下面咱们就来封装一个简单的fetch,来实现GET请求和POST请求参数的统一。
/**
* 将对象转成 a=1&b=2的形式
* @param obj 对象
*/
function obj2String(obj, arr = [], idx = 0) {
for (let item in obj) {
arr[idx++] = [item, obj[item]]
}
return new URLSearchParams(arr).toString()
}
/**
* 真正的请求
* @param url 请求地址
* @param options 请求参数
* @param method 请求方式
*/
function commonFetcdh(url, options, method = 'GET') {
const searchStr = obj2String(options)
let initObj = {}
if (method === 'GET') { // 如果是GET请求,拼接url
url += '?' + searchStr
initObj = {
method: method,
credentials: 'include'
}
} else {
initObj = {
method: method,
credentials: 'include',
headers: new Headers({
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
}),
body: searchStr
}
}
fetch(url, initObj).then((res) => {
return res.json()
}).then((res) => {
return res
})
}
/**
* GET请求
* @param url 请求地址
* @param options 请求参数
*/
function GET(url, options) {
return commonFetcdh(url, options, 'GET')
}
/**
* POST请求
* @param url 请求地址
* @param options 请求参数
*/
function POST(url, options) {
return commonFetcdh(url, options, 'POST')
}
GET('https://www.baidu.com/search/error.html', {a:1,b:2})
POST('https://www.baidu.com/search/error.html', {a:1,b:2})
Ajax
它的全称是:Asynchronous JavaScript And XML,翻译过来就是“异步的 Javascript 和 XML”。
很多小伙伴可能会误以为 Ajax 是发请求的一种方式,或者把 XMLHttpRequest 与 Ajax 划等号,其实这是错误和片面的。
正解:
Ajax 是一个技术统称,是一个概念模型,它囊括了很多技术,并不特指某一技术,它很重要的特性之一就是让页面实现局部刷新。
特点:
- 局部刷新页面,无需重载整个页面。
简单来说,Ajax 是一种思想,XMLHttpRequest 只是实现 Ajax 的一种方式。其中 XMLHttpRequest 模块就是实现 Ajax 的一种很好的方式,这也是很多面试官喜欢让面试者手撕的代码之一。
利用 XMLHttpRequest 模块实现 Ajax。
示例代码:
<body>
<script>
function ajax(url) {
const xhr = new XMLHttpRequest();
xhr.open("get", url, false);
xhr.onreadystatechange = function () {
// 异步回调函数
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.info("响应结果", xhr.response)
}
}
}
xhr.send(null);
}
ajax('https://smallpig.site/api/category/getCategory')
</script>
</body>
输出结果:
这里利用 XMLHttpRequest 模块实现了一个最简单的 get 网络请求。
注意:我们使用这种方式实现网络请求时,如果请求内部又包含请求,以此循环,就会出现回调地狱,这也是一个诟病,后来才催生了更加优雅的请求方式。
Fetch、Ajax、Axios区别
Fetch
Fetch 是在 ES6 出现的,它使用了 ES6 提出的 promise 对象。它是 XMLHttpRequest 的替代品。
很多小伙伴会把它与 Ajax 作比较,其实这是不对的,我们通常所说的 Ajax 是指使用 XMLHttpRequest 实现的 Ajax,所以真正应该和 XMLHttpRequest 作比较。
正解:
Fetch 是一个 API,它是真实存在的,它是基于 promise 的。
特点:
- 使用 promise,不使用回调函数。
- 采用模块化设计,比如 rep、res 等对象分散开来,比较友好。
- 通过数据流对象处理数据,可以提高网站性能。
所以这里就和 Ajax 又很大不同了,一个是思想,一个是真实存在的 API,不过它们都是用来给网络请求服务的,我们一起来看看利用 Fetch 实现网络请求。
示例代码:
<body>
<script>
function ajaxFetch(url) {
fetch(url).then(res => res.json()).then(data => {
console.info(data)
})
}
ajaxFetch('https://smallpig.site/api/category/getCategory')
</script>
</body>
输出结果:
上段代码利用 Fetch 发送了一个最简单的 get 请求,其中最重要的特点之一就是采用了.then 链式调用的方式处理结果,这样不仅利于代码的可读,而且也解决了回调地狱的问题。
Axios
Axios 是随着 Vue 的兴起而被广泛使用的,目前来说,绝大多数的 Vue 项目中的网络请求都是利用 Axios 发起的。当然它并不是一个思想,或者一个原生 API,它是一个封装库。
正解:
Axios 是一个基于 promise 封装的网络请求库,它是基于 XHR 进行二次封装。
特点:
- 从浏览器中创建 XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
所以说,Axios 可以说是 XHR 的一个子集,而 XHR 又是 Ajax 的一个子集。既然说它是一个库,那么我们在使用的时候就需要引入它。
示例代码:
// 发送 POST 请求
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
})
总结
Ajax、Fetch、axios三者之间的关系可以用一张图来清晰的表示,如图:
三者做个对比:
Ajax | 一种技术统称,主要利用XHR实现网络请求 |
Fetch | 具体API,基于promise,实现网络请求 |
Axios | 一个封装库,基于XHR封装,较为推荐使用 |
更多推荐
所有评论(0)