作者:Faraz Kelhini
译者:Da
原网址:blog.logrocket.com/axios-or-fe…
在我刚发布的如何像专业人士一样使用axios发出高大上的HTTP请求一文中,描述了使用Axios库的各种好处。但是,要知道Axios并不总是最好的解决方案,在有些情况下我们有比Axios更好的选择。
毫无疑问,一些开发者们相对于内置的API更青睐于axios,因为简单,好用,但是很多人都高估了对这样一个库的需求。fetch()这一内置API就能完美重现Axios的主要功能,而且它还有在所有主流浏览器中轻松使用的附加优势哟!
在这篇文章汇总,我们会对比fetch()和Axios,看他们在不同的场景下有怎样的使用方法。希望在读完这篇文章后,你能对这两者都有更多的了解。
基础语法
在深入了解Axios的高级功能之前,我们先来就基础语法与fetch()进行一个比较。下面的代码是我们使用Axios向一个URL发送自定义请求头的POST请求,因为Axios会自动将数据转换成JSON格式,我们就不用手动转换了。
// axios
const options = {
url: 'http://localhost/test.htm',
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
data: {
a: 10,
b: 20
}
};
axios(options)
.then(response => {
console.log(response.status);
});
现在我们使用fetch()重写上面的功能,然后对比一下
// fetch()
const url = 'http://localhost/test.htm';
const options = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
body: JSON.stringify({
a: 10,
b: 20
})
};
fetch(url, options)
.then(response => {
console.log(response.status);
});
注意:
- 发送数据,fetch()使用的是body属性,而Axios使用的是data属性;
- fetch()方法里的data是被字符串化的;
- 在fetch()中,URL是作为参数被使用,而在Axios中,URL是作为options的一个配置项。
向后兼容
Axios的一个主要卖点就是它兼容范围非常广,即使是在老牌浏览器中像IE11,都能不需要issue直接运行。但是fetch()就比较尴尬了,它只能支持Chrome 42+, Firefox 39+, Edge 14+, 和 Safari 10.1+ (你可以在这里看到详细的兼容表)
如果向后兼容是你决定使用Axios的唯一理由,那大可不必专门装个HTTP库,你可以像这样使用polyfill(点这里)在不支持fetch()的web浏览器上实现类似的功能。注意,在使用fetch polyfill之前,你需要先通过npm命令安装它
npm install whatwg-fetch --save
然后你就可以像这样使用fetch()啦
import 'whatwg-fetch'
window.fetch(...)
友情提示,在低版本的浏览器中,你可能同样需要装一个promise的polyfill
响应超时
开发者们更倾向于Axios的另一个原因就是:简单快捷的配置响应超时。在Axios中,你可以使用在config对象中的timeout属性设置终止请求的超时时长(毫秒),如下:
axios({
method: 'post',
url: '/login',
timeout: 4000, // 4 seconds timeout
data: {
firstName: 'David',
lastName: 'Pollock'
}
})
.then(response => {/* handle the response */})
.catch(error => console.error('timeout exceeded'))
fetch()通过AbortController实例也可以实现类似的功能,不过就是不像Axios那么方便。。。
const controller = new AbortController();
const options = {
method: 'POST',
signal: controller.signal,
body: JSON.stringify({
firstName: 'David',
lastName: 'Pollock'
})
};
const promise = fetch('/login', options);
const timeoutId = setTimeout(() => controller.abort(), 4000);
promise
.then(response => {/* handle the response */})
.catch(error => console.error('timeout exceeded'));
上文中我们通过AbortController()构造函数创建了一个AbortController对象,它的功能就是让我们可以实现延时终止请求。signal 是AbortController的一个只读属性,提供了跟请求交流或终止请求的途径。如果这个服务没有在4秒钟内响应,那么controller.abort()就会被调用,请求将会被终止。
自动转换JSON数据
我们刚刚说过,当我们发送请求时,Axios可以自动字符串化数据(你也可以覆盖默认行为,自己定义不同的转换机制)。但是当我们使用fetch()时,就不得不手动实现转换了,对比如下:
// axios
axios.get('https://api.github.com/orgs/axios')
.then(response => {
console.log(response.data);
}, error => {
console.log(error);
});
// fetch()
fetch('https://api.github.com/orgs/axios')
.then(response => response.json()) // 多出来一步
.then(data => {
console.log(data)
})
.catch(error => console.error(error));
能自动化转换数据格式当然很nice,但记住,这些事情用fetch()也可以做到。
HTTP拦截器
Axios的另一个主要特点是:它具备拦截HTTP请求的功能。在应用程序向后台服务(或者反过来的情况,像e.g,日志打印,认证方式)发送请求前,你需要检查或修改HTTP请求时,HTTP拦截器就派上用场了。用了拦截器,你就不需要为每个HTTP请求都写上相同的代码了。
以下代码教你在Axios中如何声明请求拦截器
axios.interceptors.request.use(config => {
// log a message before any HTTP request is sent
console.log('Request was sent');
return config;
});
// sent a GET request
axios.get('https://api.github.com/users/sideshowbarker')
.then(response => {
console.log(response.data);
});
在上面的代码中,axios.interceptors.request.use()就是用来定义HTTP请求发送之前运行的代码的。
在默认情况下,fetch()并不提供拦截请求的方法 ,但是找到解决方案也并非难事哦。你可以重写全局的fetch()方法,然后自己定义拦截器,就像这样:
fetch = (originalFetch => {
return (...arguments) => {
const result = originalFetch.apply(this, arguments);
return result.then(console.log('Request was sent'));
};
})(fetch);
fetch('https://api.github.com/orgs/axios')
.then(response => response.json())
.then(data => {
console.log(data)
});
下载进度
进度提示在下载大文件时是非常有用的,尤其是在用户网络比较差的时候。之前JavaScript的开发者们是使用XMLHttpRequest.onprogress回调实现进度提示的。fetch()API并没有onprogress的事件回调,作为替代方案,它通过响应对象的body属性提供了ReadableStream实例。
以下示例简单说明了使用ReadableStream实现图片下载的进度反馈
// original code: https://github.com/AnthumChris/fetch-progress-indicators
<div id="progress" src="">progress</div>
<img id="img">
<script>
'use strict'
const element = document.getElementById('progress');
fetch('https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg')
.then(response => {
if (!response.ok) {
throw Error(response.status+' '+response.statusText)
}
// ensure ReadableStream is supported
if (!response.body) {
throw Error('ReadableStream not yet supported in this browser.')
}
// store the size of the entity-body, in bytes
const contentLength = response.headers.get('content-length');
// ensure contentLength is available
if (!contentLength) {
throw Error('Content-Length response header unavailable');
}
// parse the integer into a base-10 number
const total = parseInt(contentLength, 10);
let loaded = 0;
return new Response(
// create and return a readable stream
new ReadableStream({
start(controller) {
const reader = response.body.getReader();
read();
function read() {
reader.read().then(({done, value}) => {
if (done) {
controller.close();
return;
}
loaded += value.byteLength;
progress({loaded, total})
controller.enqueue(value);
read();
}).catch(error => {
console.error(error);
controller.error(error)
})
}
}
})
);
})
.then(response =>
// construct a blob from the data
response.blob()
)
.then(data => {
// insert the downloaded image into the page
document.getElementById('img').src = URL.createObjectURL(data);
})
.catch(error => {
console.error(error);
})
function progress({loaded, total}) {
element.innerHTML = Math.round(loaded/total*100)+'%';
}
</script>
在Axios中实现进度提示很简单,尤其是配套使用 Axios Progress Bar 模块,首先,你要引入下面的样式和js
<link rel="stylesheet" type="text/css" href="https://cdn.rawgit.com/rikmms/progress-bar-4-axios/0a3acf92/dist/nprogress.css" />
<script src="https://cdn.rawgit.com/rikmms/progress-bar-4-axios/0a3acf92/dist/index.js"></script>
然后你就能像这样使用进度条啦:
<img id="img">
<script>
loadProgressBar();
const url = 'https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg';
function downloadFile(url) {
axios.get(url, {responseType: 'blob'})
.then(response => {
const reader = new window.FileReader();
reader.readAsDataURL(response.data);
reader.onload = () => {
document.getElementById('img').setAttribute('src', reader.result);
}
})
.catch(error => {
console.log(error)
});
}
downloadFile(url);
</script>
上面的代码使用了FileReader API异步读取下载的图片,readAsDataURL 方法返回图片的Base64格式字符串,后面会被插进img的src属性中,将图片展示出来。
同时发起多个请求
当我们需要同时发起多个请求时,Axios提供了axios.all()方法,只需要将请求数组发送给这个方法,然后使用axios.spread()将响应数组的属性分配给一个个的变量:
axios.all([
axios.get('https://api.github.com/users/iliakan'),
axios.get('https://api.github.com/users/taylorotwell')
])
.then(axios.spread((obj1, obj2) => {
// Both requests are now complete
console.log(obj1.data.login + ' has ' + obj1.data.public_repos + ' public repos on GitHub');
console.log(obj2.data.login + ' has ' + obj2.data.public_repos + ' public repos on GitHub');
}));
你同样可以通过内置的Promise.all()方法实现上述功能,将所有请求作为数组传给promise.all(),然后使用async 处理这些响应,如下:
Promise.all([
fetch('https://api.github.com/users/iliakan'),
fetch('https://api.github.com/users/taylorotwell')
])
.then(async([res1, res2]) => {
const a = await res1.json();
const b = await res2.json();
console.log(a.login + ' has ' + a.public_repos + ' public repos on GitHub');
console.log(b.login + ' has ' + b.public_repos + ' public repos on GitHub');
})
.catch(error => {
console.log(error);
});
总结
Axios通过紧凑的代码包给我们提供了很多易于使用的API,满足我们大部分的HTTP通讯场景。但是,如果你更愿意坚持使用原生API,没有什么事情可以阻挡你的步伐!你可以自己实现Axios那些特性哦!
如本文所述,使用web浏览器提供的fetch()方法可以完美重写Axios库提供的那些主要功能。最终,是否需要一个HTTP库完全取决于你使用内置API是否感觉舒适。