Ajax -> Axios -> Fetch

316 阅读2分钟

Ajax相信大家都不陌生了,从刚开始的前后端分离,我们前端想要从服务器获取数据,就得经过Ajax来获取

let xhr = new XNLHttpRequest();
xhr.open();
xhr.onreadystatechange=function(){
    //...
}
xhr.send();

我们大体上会经过这四个步骤来向服务器获取数据,后来估计太麻烦了,JQuery封装了Ajax,使我们获取数据获得了极大的便利

详见jquery文档:jquery.cuishifeng.cn/jQuery.Ajax…

$.ajax({
        url:'',
        method:'GET',
        async:false,
        dataType:'json',
        success:result=>{
            //result:当请求成功执行success函数,result就是从服务器获取的结果
            _DATA=result;
        }
    })

虽然这样少些了很多步骤,但是项目中遇到的Ajax串行,并行,我们要避免回调地狱这个大问题!后来,ES6语法规范中出现了一个新的内置类Promise,我们可以使用then链,来处理JS异步编程。

Axios

Axios是基于Ajax和Promise封装的库,其核心还是Ajax,只是封装的时候用了Promise模式

在后来的项目中,我们会基于Axios向后台接口发送请求。我一般对Axios进行第二次封装。

Axios中文文档:www.axios-js.com/zh-cn/docs/

import axios from 'axios';
import qs from 'qs';

/* 
 * 根据环境变量进行接口区分
 */
switch (process.env.NODE_ENV) {
    case "development":
        axios.defaults.baseURL = "http://127.0.0.1:9000";
        break;
    case "test":
        axios.defaults.baseURL = "http://192.168.20.15:9000";
        break;
    case "production":
        axios.defaults.baseURL = "http://api.zhufengpeixun.cn";
        break;
}

/*
 * 设置超时请求时间 
 */
axios.defaults.timeout = 10000;

/*
 * 设置CORS跨域允许携带资源凭证
 */
axios.defaults.withCredentials = true;

/*
 * 设置POST请求头:告知服务器请求主体的数据格式
 */
axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.transformRequest = data => qs.stringify(data);

/*
 * 设置请求拦截器  [ˌɪntərˈsɛptərz]
 */
axios.interceptors.request.use(config => {
    // 添加TOKEN验证:可以从本地存储中取值,也可以从VUEX中取值,主要看怎么存储的
    // JWT实现TOKEN校验  [ˌɔːθəraɪˈzeɪʃn]
    const token = localStorage.getItem('token');
    token && (config.headers.Authorization = token);
    return config;
}, error => {
    return Promise.reject(error);
});

/*
 * 设置响应拦截器 
 */
axios.defaults.validateStatus = status => {
    // 自定义响应成功的HTTP状态码
    return /^(2|3)\d{2}$/.test(status);
};
axios.interceptors.response.use(response => {
    // 只返回响应主体中的信息(部分公司根据需求会进一步完善,例如指定服务器返回的CODE值来指定成功还是失败)
    return response.data;
}, error => {
    if (error.response) {
        // 请求已发送,只不过状态码不是200系列,设置不同状态码的不同处理
        switch (error.response.status) {
            case 401: // 当前请求需要用户验证(一般是未登录)
                break;
            case 403: // 服务器已经理解请求,但是拒绝执行它(一般是TOKEN过期)
                localStorage.removeItem('token');
                // 跳转到登录页
                break;
            case 404: // 请求失败,请求所希望得到的资源未被在服务器上发现
                break;
        }
        return Promise.reject(error.response);
    } else {
        // 断网处理
        if (!window.navigator.onLine) {
            // 断开网络了,可以让其跳转到断网页面
            return;
        }
        return Promise.reject(error);
    }
});

export default axios;

Fetch

Fetch Api:developer.mozilla.org/zh-CN/docs/…

后来浏览器内置的Api:Fetch出现了,它和Ajax不是一个东西,是两套完全不同的机制,并且Fetch天生就是基于Promise进行管理的。

所以现在向服务器发送请求有两种方式:一种是XNLHttpRequest的Ajax方案,第二种就是基于Fetch

fetch([url],[options]).then(response=>{

});

Fetch库的第二次封装

import qs from 'qs';
/*
 * 根据环境变量进行接口区分
 */
let baseURL = '';
let baseURLArr = [{
    type: 'development',
    url: 'http://127.0.0.1:9000'
}, {
    type: 'test',
    url: 'http://192.168.20.15:9000'
}, {
    type: 'production',
    url: 'http://api.zhufengpeixun.cn'
}];
baseURLArr.forEach(item => {
    if (process.env.NODE_ENV === item.type) {
        baseURL = item.url;
    }
});

export default function request(url, options = {}) {
    url = baseURL + url;
    /*
     * GET系列请求的处理 
     */
    !options.method ? options.method = 'GET' : null;
    if (options.hasOwnProperty('params')) {
        if (/^(GET|DELETE|HEAD|OPTIONS)$/i.test(options.method)) {
            const ask = url.includes('?') ? '&' : '?';
            url += `${ask}${qs.stringify(params)}`;
        }
        delete options.params;
    }

    /*
     * 合并配置项 
     */
    options = Object.assign({
        // 允许跨域携带资源凭证 same-origin同源可以  omit都拒绝
        credentials: 'include',
        // 设置请求头
        headers: {}
    }, options);
    options.headers.Accept = 'application/json';

    /*
     * token的校验
     */
    const token = localStorage.getItem('token');
    token && (options.headers.Authorization = token);

    /*
     * POST请求的处理
     */
    if (/^(POST|PUT)$/i.test(options.method)) {
        !options.type ? options.type = 'urlencoded' : null;
        if (options.type === 'urlencoded') {
            options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
            options.body = qs.stringify(options.body);
        }
        if (options.type === 'json') {
            options.headers['Content-Type'] = 'application/json';
            options.body = JSON.stringify(options.body);
        }
    }

    return fetch(url, options).then(response => {
        // 返回的结果可能是非200状态码
        if (!/^(2|3)\d{2}$/.test(response.status)) {
            switch (response.status) {
                case 401: // 当前请求需要用户验证(一般是未登录)
                    break;
                case 403: // 服务器已经理解请求,但是拒绝执行它(一般是TOKEN过期)
                    localStorage.removeItem('token');
                    // 跳转到登录页
                    break;
                case 404: // 请求失败,请求所希望得到的资源未被在服务器上发现
                    break;
            }
            return Promise.reject(response);
        }
        return response.json();
    }).catch(error => {
        // 断网处理
        if (!window.navigator.onLine) {
            // 断开网络了,可以让其跳转到断网页面
            return;
        }
        return Promise.reject(error);
    });
};

在项目中,我一般会对api的请求接口进行模块化管理,例如:

定义统一入口 api.js

import Home from './home';
export default {
    Home
};

单独模块管理接口

import axios from './http';//对Axios的第二次封装
export default {
    login() {
        return axios.post('/login');
    },
    // ...
};

最后注入到Vue的原型上

import api from './api/api.js';
Vue.prototype.$api=api;

//=>使用
this.$api.Home.login().then(result=>{
    //...
});