这两天,项目中用到axios,踩到了各种坑,也体验到了新的代码逻辑思路
axios与ajax
axios是通过promise实现对ajax技术的一种封装,就像jQuery实现ajax封装一样。
简单来说: ajax技术实现了网页的局部数据刷新,axios实现了对ajax的封装。
浏览器端发起XMLHttpRequests请求、node端发起http请求、支持Promise AP、客户端支持抵御XSRF
在vue2.0中,官方推荐是采用axios方案
axios文档
中文文档:http://www.axios-js.com/zh-cn/docs/
原文档:https://www.npmjs.com/package/axios
axios封装方案
axios总封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
function axiosAdapter (config) { return new Promise((resolve, reject) => { //resolve供.then调用,reject供catch调用 axios(config).then((response) => { resolve(response); //保存到成功的返回resolve中,共.then调用 }).catch((error) => { //异常处理 console.log("response error :" + error); var config = error.config; //TODO ADD if(error.config.method=="delete"){ //如果请求方法是delete reject(error); //将error保存到reject返回中 return; //结束异常处理 } if (config._router && config._router.authorized) { //如果有授权 if (error.response && error.response.status === 401) { //如果返回401 if (error.response.statusText === 'kicked_out') { relogin(config, resolve, reject, true); //如果被踢下线,重新登陆 } else { refresh(config, resolve, reject); } } else { relogin(config, resolve, reject, true); } return new Promise(() => { //返回空的Promise,表示处理完成 }); } return Promise.reject(error) // 在console中报错,终止运行,表示被拦截 }); }); } |
配置项替换函数,可以实现src替换tar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function _copy(tar, src) { if (!(src instanceof Object)) { return; } for (let name in src) { if (src.hasOwnProperty(name)) { let obj = src[name]; if (obj instanceof Object) { tar[name] = tar[name] || {}; _copy(tar[name], obj); } else { tar[name] = obj; } } } } |
POST封装(可以通过config替换或新增内容)
1 2 3 4 5 6 7 8 9 10 11 12 |
function _post (url, data, config) { var _config = { url: url, method: 'post', data: Qs.stringify(data), headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }; _copy(_config, config); return axiosAdapter(_config); } |
GET封装(可以通过config替换或新增内容)
1 2 3 4 5 6 7 8 9 10 11 12 |
function _get (url, data, config) { var _config = { url: url, method: 'get', params: data, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }; _copy(_config, config); return axiosAdapter(_config); } |
最后注册到全局
1 2 3 |
window.$post = _post; window.$get = _get; window.$ajax = _ajax; |
axios按顺序调用思路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function getToken(data){ var url = geValue1(data); var code = geValue2(url); var token = geValue3(code); return token; } function getValue1(data){ //GET VALUE=>result console.log(1); return result; } function getValue2(data){ //GET VALUE=>result console.log(2); return result; } function getValue3(data){ //GET VALUE=>result console.log(3); return result; } |
在ajax使用中,如果对于一个函数需要返回值(如上所示),我们可以在ajax中设置async:false,从而达到同步访问的效果,ajax同步执行后,便可以成功返回值
但是axios为异步请求方案,不支持类似于ajax这样的设置,这个时候如果在GET VALUE中使用axios方法,则执行的顺序为先return后TODO中的axios,故此时返回的值为undefine,最后url code token都得不到理想的顺序执行的值
如果要建立同步的请求顺序,常见有两种方式
其一,采用async/await
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
data:{ data:[] } methods: { async getToken(){ var url = await geValue1(data); var code = await geValue2(url); var token = await geValue3(code); return token; }, funA(){ var token = getToken(); alert(token); getData(); return; }, async getData(){ var url = await geValue1(data); var code = await geValue2(url); var token = await geValue3(code); this.data.push() } } |
看上去问题好像解决了,但实际上getToken变成了异步的函数了!funA函数执行顺序发生了变化,alert中无法正确的显示token值。但由于getData绑定了数据data,因此虽然该函数也是异步执行的,但是当该函数执行完后,可以正确的刷新data数据值,由于用了push方法因此可以局部刷新视图,从而实现数据更新。
其二,.then嵌套
1 2 3 4 5 6 7 8 9 10 11 |
getCode() { var code; var thiz = this; var sendData={ response_type: 'code', client_id: 'jupyter_web'}; var config={headers: {'Authorization': 'Bearer '+this.$cookies.get("access_token")}}; return $get("/AuthenticationManager/oauth/authorize" , sendData , config) //返回一个axios请求,返回值为Promise类型 .then( (result)=> { thiz.parm.code = result.data.code; //将code值保存在数据中 console.log("已经获取到code"); }); }, |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
getToken() { var userId = this.getUserId(); if(this.parm.token!=''){ //表示数据源中已有,则跳过 return this.parm.token; } var thiz = this; var baseUrl = this.$jupyter_hub_url; return this.getCode() //this.getCode() 为上一个函数中的axios请求,类型为Promise .then( (result) =>{ //表示先等上面getCode请求完成之后再执行 console.log(thiz.parm.code); var config={contentType: "application/json;charset=utf-8"}; return $post(baseUrl + userId + "/tokens?code=" + thiz.parm.code,null,config) //用于替代.then,相当于建立了一个执行顺序,先getCode,后post带这个code的内容 .then((result)=> { console.log('[Jupyterhub] - TOKEN'); console.log(result.data); thiz.parm.token = result.data.token; thiz.setCookie('token', thiz.parm.token, '1'); }); }); }, |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
this.getToken() //调用上面一个axios,展开为 $get(code).then(保存code).then(post带code的参数,得到token).then(保存token) .then(function (data) { var config={headers: {'Authorization': 'token ' + token}}; $get(url,null,config) .then(result=>{ if(result.data.server == null){ console.log("服务启动") return $post(hubUrl + userId + "/server",null,config) //可以理解为返回一个新的post axios请求,替代.then }else{ console.log("服务无需启动") return; //与上面对应,此时不需要post,故直接return控制,替代.then,表示这个.then过程跳过 } }) .then(res=>{ $get(baseUrl + userId + '/api/contents',null,config) .then(result=>{ //依次执行,.then一定在请求完成后开始处理 var fileInfo; thiz.data.push(result.data); //更新数据 } }) }) }) |
$get(code).then(保存code).then(post带code的参数,得到token).then(保存token).then(get服务器状态).then(判断状态,如果未启动则post启动,否则return).then(get目录).then(处理目录)
思路技巧
.then中可以嵌套axios方法,如果return出来,就可以理解为替代了这个.then,由于.then为顺序执行,故这两个axios请求为顺序执行,下一个请求可以调用上一个请求的值
.then也可以return空,表示无返回,跳过这个.then,常用在判断中
中间产生的数据可以保存在data中,这样可以用this.data轻松访问
参考资料:https://stackoverflow.com/questions/46347778/how-to-make-axios-synchronous
Promise使用
Promise
对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。 这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象
一个 Promise
有以下几种状态:
- pending: 初始状态,既不是成功,也不是失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
pending 状态的 Promise 对象可能会变为fulfilled 状态并传递一个值给相应的状态处理方法,也可能变为失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then
方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。
创建方法:
1 2 3 4 5 6 |
new Promise((resolve, reject) => { axios(config).then((response) => { resolve(response); //重新定义正确返回的值的形式 }).catch((error) => { reject(error); //重新定义错误返回的形式 } |
其中resolve可以理解为用于保存正确返回时的响应,在.then(result=>{})中,可以用result获取到
reject可以理解为异常的响应,可以用.catch(error=>{}),error获取到
如果要改变返回的形式,可以使用resolve函数和reject函数分别修改正确返回和异常返回的值
而下面的方法可以使得请求被拦截,直接在console中被拦截
1 |
Promise.reject(error) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
new Promise((resolve, reject)=> { log('start new Promise...'); var timeOut = Math.random() * 2; log('set timeout to: ' + timeOut + ' seconds.'); setTimeout(function () { if (timeOut < 1) { log('call resolve()...'); resolve('200 OK'); } else { log('call reject()...'); reject('timeout in ' + timeOut + ' seconds.'); } }, timeOut * 1000); }).then(function (r) { log('Done: ' + r); }).catch(function (reason) { log('Failed: ' + reason); }); |
Promise还可以做更多的事情,比如,有若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败则不再继续并执行错误处理函数。
要串行执行这样的异步任务,不用Promise需要写一层一层的嵌套代码。有了Promise,我们只需要简单地写:
1 |
job1.then(job2).then(job3).catch(handleError); |
其中,job1
、job2
和job3
都是Promise对象。
除了串行执行若干异步任务外,Promise还可以并行执行异步任务。
试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()
实现如下:
1 2 3 4 5 6 7 8 9 10 11 |
<span class="keyword">var</span> p1 = <span class="keyword">new</span> Promise(<span class="function"><span class="keyword">function</span> <span class="params">(resolve, reject)</span> {</span> setTimeout(resolve, <span class="number">500</span>, <span class="string">'P1'</span>); }); <span class="keyword">var</span> p2 = <span class="keyword">new</span> Promise(<span class="function"><span class="keyword">function</span> <span class="params">(resolve, reject)</span> {</span> setTimeout(resolve, <span class="number">600</span>, <span class="string">'P2'</span>); }); <span class="comment">// 同时执行p1和p2,并在它们都完成后执行then:</span> Promise.all([p1, p2]).then(<span class="function"><span class="keyword">function</span> <span class="params">(results)</span> {</span> console.log(results); <span class="comment">// 获得一个Array: ['P1', 'P2']</span> }); |
有些时候,多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()
实现:
1 2 3 4 5 6 7 8 9 10 |
<span class="keyword">var</span> p1 = <span class="keyword">new</span> Promise(<span class="function"><span class="keyword">function</span> <span class="params">(resolve, reject)</span> {</span> setTimeout(resolve, <span class="number">500</span>, <span class="string">'P1'</span>); }); <span class="keyword">var</span> p2 = <span class="keyword">new</span> Promise(<span class="function"><span class="keyword">function</span> <span class="params">(resolve, reject)</span> {</span> setTimeout(resolve, <span class="number">600</span>, <span class="string">'P2'</span>); }); Promise.race([p1, p2]).then(<span class="function"><span class="keyword">function</span> <span class="params">(result)</span> {</span> console.log(result); <span class="comment">// 'P1'</span> }); |
由于p1
执行较快,Promise的then()
将获得结果'P1'
。p2
仍在继续执行,但执行结果将被丢弃。
如果我们组合使用Promise,就可以把很多异步任务以并行和串行的方式组合起来执行。
参考资料1:https://www.liaoxuefeng.com/wiki/1022910821149312/1023024413276544
参考资料2:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise