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

1.什么是ajax请求

AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。

AJAX 不是新的编程语言,而是一种使用现有标准的新方法。

AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。

AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行

Ajax就是用 JS 发起一个请求,并得到服务器返回的内容。这跟以前的技术最大的不同点在于「页面没有刷新」,改善了用户体验,仅此而已。

那么我们如何发送一个ajax请求呢?

1. 创建一个对象 XMLHttpRequest

var xhr = new XMLHttpRequest();为了支持ie6以及更早的版本,要 var xhr=new ActiveXObject()

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的基础知识。

一、 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。

二、 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的链式执行,这也是解决回调地狱的主要方式。

三、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最常用的一个场景,因此我们一定要非常非常熟悉它,这样才能在应用的时候更加灵活。

四、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);
})

五、 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.源代码使用说明

如若转载文章请注明出处,文章链接https://blog.csdn.net/zhiy9ong/article/details/86569230源代码可以自由参考复制使用。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值