作者:Faraz Kelhini
译者:Da
原网址:blog.logrocket.com/axios-or-fe…
在我刚发布的如何像专业人士一样使用axios发出高大上的HTTPq请求一文中,描述了使用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); }); 复制代码
注意:
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()就会被调用,请求将会被终止。
我们刚刚说过,当我们发送请求时,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()也可以做到。
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是否感觉舒适。