1.什么是ajax请求javascript
AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。html
AJAX 不是新的编程语言,而是一种使用现有标准的新方法。vue
AJAX 最大的优势是在不从新加载整个页面的状况下,能够与服务器交换数据并更新部分网页内容。java
AJAX 不须要任何浏览器插件,但须要用户容许JavaScript在浏览器上执行node
Ajax就是用 JS 发起一个请求,并获得服务器返回的内容。这跟之前的技术最大的不一样点在于「页面没有刷新」,改善了用户体验,仅此而已。ios
那么咱们如何发送一个ajax请求呢?ajax
1. 建立一个对象 XMLHttpRequestvuex
var xhr = new XMLHttpRequest();为了支持ie6以及更早的版本,要 var xhr=new ActiveXObject()vue-cli
2.监听请求成功后的状态变化编程
3.设置请求参数
4.发起请求
5.操做DOM,实现动态局部刷新
// 简单的ajax原生实现 var url = '请求url'; var result; var XHR = new XMLHttpRequest(); XHR.open('GET', url, true); XHR.send(); XHR.onreadystatechange = function() { if (XHR.readyState == 4 && XHR.status == 200) { result = XHR.response; console.log(result); } }
接下来,咱们就要监听请求成功的状态变化了
onreadystatechange:用来监听readyState的变化的
readyState:表示当前请求的后台的状态
status:表示处理的结果
其中readyState:表示当前请求的后台的状态
0:请求未初始化(尚未调用open())
1:请求已经创建,可是尚未发送(尚未调用send())
2:请求已经发送,正在处理中
3:请求正在处理中,一般响应中已经有部分数据能够用了
4:响应已经完成,能够获取并使用服务器的响应了
而status:表示处理的结果(状态码)
1XX,表示收到请求正在处理中
status == 200 是表示处理的结果是OK的
状态码:200到300是指服务端正常返回
304:若是网页自请求者上次请求后再也没有更改过,应将服务器配置为返回此响应,进而节省带宽和开销
404:找不到对象(404 not found)
503:服务器超时
设置请求参数
xhr对象接受三个参数
1:表示请求类型
2:表示请求的网址
3:表示是否异步
get/post/put/delete
Get和post方法的区别:
get是获取数据,get的send方法的参数能够是null或者空,对发送信息有限制,通常在2000个字符,通常是用来查询(幂等)
post能够发送数据,可是在使用post方法发送数据,须要使用setRequestHeader()来添加HTTP头,同时,post的send()方法须要写入要发送的数据的值, 通常用于修改服务器上的资源,对信息数量无限制,也更安全
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
application/x-www-form-urlencoded表明什么意思?
form的enctype属性为编码方式,经常使用有两种:application/x-www-form-urlencoded和multipart/form-data,默认为application/x-www-form-urlencoded。
x-www-form-urlencoded当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2…),而后把这个字串append到url后面,用?分割,加载这个新的url。
使用post提交须要忘记content-type的问题
4.解决方案
xhr.open("post", "/carrots-admin-ajax/a/login",true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("name=" + name + "&pwd=" + code);
Content-type要做为请求头放在open和send之间
2.回调地狱
在ajax的原生实现中,利用了onreadystatechange事件,当该事件触发而且符合必定条件时,才能拿到咱们想要的数据,以后咱们才能开始处理数据。
这样作看上去并无什么麻烦,可是若是这个时候,咱们还须要作另一个ajax请求,这个新的ajax请求的其中一个参数,得从上一个ajax请求中获取,这个时候咱们就不得不以下这样作:
var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10'; var result; var XHR = new XMLHttpRequest(); XHR.open('GET', url, true); XHR.send(); XHR.onreadystatechange = function() { if (XHR.readyState == 4 && XHR.status == 200) { result = XHR.response; console.log(result); // 伪代码 var url2 = 'http:xxx.yyy.com/zzz?ddd=' + result.someParams; var XHR2 = new XMLHttpRequest(); XHR2.open('GET', url, true); XHR2.send(); XHR2.onreadystatechange = function() { ... } } }
当出现第三个ajax(甚至更多)仍然依赖上一个请求的时候,咱们的代码就变成了一场灾难。这场灾难,每每也被称为回调地狱。
所以咱们须要一个叫作Promise的东西,来解决这个问题。
固然,除了回调地狱以外,还有一个很是重要的需求:为了咱们的代码更加具备可读性和可维护性,咱们须要将数据请求与数据处理明确的区分开来。上面的写法,是彻底没有区分开,当数据变得复杂时,也许咱们本身都没法轻松维护本身的代码了。
当咱们想要确保某代码在谁谁以后执行时,咱们能够利用函数调用栈,将咱们想要执行的代码放入回调函数中。
// 一个简单的封装 function want() { console.log('这是你想要执行的代码'); } function fn(want) { console.log('这里表示执行了一大堆各类代码'); // 其余代码执行完毕,最后执行回调函数 want && want(); } fn(want);
确保咱们想要的代码压后执行,除了利用函数调用栈的执行顺序以外,咱们还能够利用队列机制。
function want() { console.log('这是你想要执行的代码'); } function fn(want) { // 将想要执行的代码放入队列中,根据事件循环的机制,咱们就不用非得将它放到最后面了,由你自由选择 want && setTimeout(want, 0); console.log('这里表示执行了一大堆各类代码'); } fn(want);
若是浏览器已经支持了原生的Promise对象,那么咱们就知道,浏览器的js引擎里已经有了Promise队列,这样就能够利用Promise将任务放在它的队列中去。
function want() { console.log('这是你想要执行的代码'); } function fn(want) { console.log('这里表示执行了一大堆各类代码'); // 返回Promise对象 return new Promise(function(resolve, reject) { if (typeof want == 'function') { resolve(want); } else { reject('TypeError: '+ want +'不是一个函数') } }) } fn(want).then(function(want) { want(); }) fn('1234').catch(function(err) { console.log(err); })
看上去变得更加复杂了。但是代码变得更加健壮,处理了错误输入的状况。
3.promise
为了更好的往下扩展Promise的应用,这里须要先跟你们介绍一下Promsie的基础知识。
1、 Promise对象有三种状态,他们分别是:
这三种状态不受外界影响,并且状态只能从pending改变为resolved或者rejected,而且不可逆。在Promise对象的构造函数中,将一个函数做为第一个参数。而这个函数,就是用来处理Promise的状态变化。
new Promise(function(resolve, reject) { if(true) { resolve() }; if(false) { reject() }; })
上面的resolve和reject都为一个函数,他们的做用分别是将状态修改成resolved和rejected。
2、 Promise对象中的then方法,能够接收构造函数中处理的状态变化,并分别对应执行。then方法有2个参数,第一个函数接收resolved状态的执行,第二个参数接收reject状态的执行。
function fn(num) { return new Promise(function(resolve, reject) { if (typeof num == 'number') { resolve(); } else { reject(); } }).then(function() { console.log('参数是一个number值'); }, function() { console.log('参数不是一个number值'); }) } fn('hahha'); fn(1234);
then方法的执行结果也会返回一个Promise对象。所以咱们能够进行then的链式执行,这也是解决回调地狱的主要方式。
3、Promise中的数据传递
利用Promise的知识,对最开始的ajax的例子进行一个简单的封装
var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10'; // 封装一个get请求的方法 function getJSON(url) { return new Promise(function(resolve, reject) { var XHR = new XMLHttpRequest(); XHR.open('GET', url, true); XHR.send(); XHR.onreadystatechange = function() { if (XHR.readyState == 4) { if (XHR.status == 200) { try { var response = JSON.parse(XHR.responseText); resolve(response); } catch (e) { reject(e); } } else { reject(new Error(XHR.statusText)); } } } }) } getJSON(url).then(resp => console.log(resp));
为了健壮性,处理了不少可能出现的异常,总之,就是正确的返回结果,就resolve一下,错误的返回结果,就reject一下。而且利用上面的参数传递的方式,将正确结果或者错误信息经过他们的参数传递出来。
如今全部的库几乎都将ajax请求利用Promise进行了封装,所以咱们在使用jQuery等库中的ajax请求时,均可以利用Promise来让咱们的代码更加优雅和简单。这也是Promise最经常使用的一个场景,所以咱们必定要很是很是熟悉它,这样才能在应用的时候更加灵活。
4、Promise.all
当有一个ajax请求,它的参数须要另外2个甚至更多请求都有返回结果以后才能肯定,那么这个时候,就须要用到Promise.all来帮助咱们应对这个场景。
Promise.all接收一个Promise对象组成的数组做为参数,当这个数组全部的Promise对象状态都变成resolved或者rejected的时候,它才会去调用then方法。
var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10'; var url1 = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-03-26/2017-06-10'; function renderAll() { return Promise.all([getJSON(url), getJSON(url1)]); } renderAll().then(function(value) { console.log(value); })
5、 Promise.race
与Promise.all类似的是,Promise.race都是以一个Promise对象组成的数组做为参数,不一样的是,只要当数组中的其中一个Promsie状态变成resolved或者rejected时,就能够调用.then方法了。而传递给then方法的值也会有所不一样,你们能够再浏览器中运行下面的例子与上面的例子进行对比。
function renderRace() { return Promise.race([getJSON(url), getJSON(url1)]); } renderRace().then(function(value) { console.log(value); })
4.axios的功能优点
在vue项目中,和后台交互获取数据这块,咱们一般使用的是axios库,它是基于promise的http库,可运行在浏览器端和node.js中。他有不少优秀的特性,例如
官方文档中的描述:
因此vue官方果断放弃了对其官方库vue-resource的维护,直接推荐咱们使用axios库。
5.为何须要对axios进行再一次封装
使用vue-cli建立的项目中,咱们须要模块化的集中统一处理ajax请求,好比统一进行请求和响应拦截处理,结合vue的特性能够实现全局的loading和登陆控制以及身份验证等操做,经过将数据请求和数据处理进行拆分,达到模块化的目的,方便后期维护。
6.具体过程
项目封装完成代码结构图:
代码的结构说明,http文件夹存放的就是封装axios的代码,knowledge和menu文件夹存放的是业务模块的请求代码,
base.js存放的是项目请求地址分别分为测试地址和生产环境地址,common.js存放的是利用promise进一步封装的ajax请求,config.js存放的是后台返回的状态值不是状态码,
http.js就是封装axios的基础逻辑代码,index.js是存放导出模块的代码。
看到文章前面基础部分的讲解和代码注释以及熟悉vue全家桶技术,此处不在解释每一个文件代码的做用,每一个文件的源代码以下:
http.js /* 封装axios */ import axios from 'axios'; import store from '../store/index'; let CancelToken = axios.CancelToken; let cancel; // 建立axios实例 let instance = axios.create({timeout: 5000}); // 设置post请求头 instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'; /** * 请求拦截器 * 简单的防止恶意程序批量刷接口 * 每次请求前进行时间判断处理,两次请求的时间间隔小于500ms就取消这次请求,大于500ms才发送 */ instance.interceptors.request.use( config => { config.cancelToken = new CancelToken(function (c) { cancel = c; }); let time = new Date().getTime(); let storeTime = store.state.time; let gapTime = time - storeTime; if (gapTime < 500) { cancel('请求过于频繁'); } store.commit('setTime', time); store.commit('showLoading'); store.commit('hideNetwork'); // 隐藏断网界面 return config; }, error => { Promise.error(error); }); // 响应拦截器 instance.interceptors.response.use( // 请求成功 res => { store.commit('hideLoading'); store.commit('hideNetwork'); return res.status === 200 ? Promise.resolve(res) : Promise.reject(res); }, // 请求失败 error => { const {response} = error; store.commit('hideLoading'); if (response) { return Promise.reject(response); } else { store.commit('showNetwork'); // 显示断网界面 } }); export default instance;
base.js /** * 接口域名的管理 */ const base = { cs: 'http://xxxxxxxxxxx:9999/api', pd: 'http://xxxxxxxxxxxx:9999/api' }; export default base; common.js /* 通用处理 */ import base from './base'; // 导入接口域名列表 import axios from './http'; // 导入http中建立的axios实例 const common = { post (url, params) { return new Promise((resolve, reject) => { axios.post(`${base.cs}${url}`, JSON.stringify(params)).then(res => { resolve(res.data); }, err => { reject(err.message); }); }); }, get (url, params) { return new Promise((resolve, reject) => { axios.get(`${base.cs}${url}`, { params: params }).then(res => { resolve(res.data); }, err => { reject(err.message); }); }); } }; export default common; config.js // 请求成功后台返回正确码 export const ERR_OK = 1; // 请求成功但后台返回错误码 export const ERR_NO = -1; menu.js 业务逻辑 /** * 知识库接口列表 */ import common from '../common'; const menu = { // 获取菜单树形结构 getMenuTreeList (params) { return common.post('/menu/getusermenu', params); } }; export default menu; index.js /** * api接口的统一出口 */ import knowledge from './knowledge/knowledge'; import menu from './menu/menu'; // 导出接口 export default { knowledge, menu }; main.js中进行导入和挂载到vue的全局属性上 import api from 'http/index'; ... Vue.prototype.$api = api; app.vue .... <transition name="net"> <div v-if="netWork" class="network"> <div class="network-gif"></div> <h3>你的网络状况好像不太好,请点击刷新试试吧............</h3> <div class="network-refresh" @click="onRefresh">刷新</div> </div> </transition> .... // js代码 import { mapState } from 'vuex'; import Loading from 'components/loading/Loading.vue'; export default { name: 'App', computed: { ...mapState([ 'loading', 'netWork' ]) }, methods: { onRefresh () { this.$router.replace('/refresh'); } }, components: { Loading } }; refresh.vue 实现刷新的空界面 <template> <div class="refresh"> </div> </template> <script type="text/ecmascript-6"> export default { name: 'Refresh', beforeRouteEnter (to, from, next) { next(vm => { vm.$router.replace(from.fullPath); }); } }; </script> <style lang="stylus" rel="stylesheet/stylus" scoped> </style> 至此axios的封装代码已经结束,下面是如何使用示例 示例代码: // 组件中导入 import { ERR_OK } from 'http/config'; this.$api.menu.getMenuTreeList({login: 'admin'}).then(res => { if (res.success === ERR_OK) { this.menuList = res.menu; } }).catch(err => { this.$Message.error(err); });
7.源代码使用说明
如若转载文章请注明出处,文章连接http://www.javashuo.com/article/p-xjjfkaqz-ew.html,源代码能够自由参考复制使用。