Vue项目中axios的网络请求的封装实例

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(Fulfilled): 已经完成,表示获得了咱们想要的结果,能够继续往下执行
  • rejected: 也表示获得结果,可是因为结果并不是咱们所愿,所以拒绝执行

这三种状态不受外界影响,并且状态只能从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中。他有不少优秀的特性,例如

官方文档中的描述:

  • Make XMLHttpRequests from the browser            经过浏览器发送XMLHttpRequest请求
  • Make http requests from node.js                               node.js中发送http请求
  • Supports the Promise API                                        支持Promise请求的API
  • Intercept request and response                                 请求拦截和响应拦截
  • Transform request and response data                       转换请求和响应中的数据格式
  • Cancel requests                                                          取消请求
  • Automatic transforms for JSON data                          自动转换JSON数据格式
  • Client side support for protecting against XSRF        客户端防护XSRF

因此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源代码能够自由参考复制使用。