从Ajax到Axios解析

XMLHttpRequest 对象

const xhr = new XMLHttpRequest();

xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {}
};

const url = "http://www.baidu.com/";
xhr.open("post", url, true); //初始化
const body = {
    data: {
        a: 1
    }
};

xhr.send(body);

xhr.onload = function() {
    // 请求结束后, 在此处写处理代码
    console.log('请求成功', xhr.responseText)
};

// 当网络不佳时,我们需要给请求设置一个超时时间

// 超时时间单位为毫秒
xhr.timeout = 1000

// 当请求超时时,会触发 ontimeout 方法
xhr.ontimeout = () => console.log('请求超时')
  • xhr.readyStatus==0 尚未调用 open 方法

  • xhr.readyStatus==1 已调用 open 但还未发送请求(未调用 send)

  • xhr.readyStatus==2 已发送请求(已调用 send)

  • xhr.readyStatus==3 已接收到请求返回的数据

  • xhr.readyStatus==4 请求已完成

ajax

function ajax(options) {
    let url = options.url;
    const method = options.method.toLocaleLowerCase() || "get";
    const async = options.async == true; // default is true
    const data = options.data;
    const xhr = new XMLHttpRequest();

    if (options.timeout && options.timeout > 0) {
        xhr.timeout = options.timeout;
    }

    return new Promise((resolve, reject) => {
        xhr.ontimeout = () => reject && reject("请求超时");
        xhr.onreadystatechange = () => {
            if (xhr.readyState == 4) {
                if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
                    resolve && resolve(xhr.responseText);
                } else {
                    reject && reject();
                }
            }
        };
        xhr.onerror = (err) => reject && reject(err);

        let paramArr = [];
        let encodeData;
        if (data instanceof Object) {
            for (let key in data) {
                // 参数拼接需要通过 encodeURIComponent 进行编码
                paramArr.push(
                    encodeURIComponent(key) + "=" + encodeURIComponent(data[key])
                );
            }
            encodeData = paramArr.join("&");
        }

        if (method === "get") {
            // 检测 url 中是否已存在 ? 及其位置
            const index = url.indexOf("?");
            if (index === -1) url += "?";
            else if (index !== url.length - 1) url += "&";
            // 拼接 url
            url += encodeData;
        }

        xhr.open(method, url, async);
        if (method === "get") xhr.send(null);
        else {
            // post 方式需要设置请求头
            xhr.setRequestHeader(
                "Content-Type",
                "application/x-www-form-urlencoded;charset=UTF-8"
            );
            xhr.send(encodeData);
        }
    });
}

fetch

// 原生XHR
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
        console.log(xhr.responseText) // 从服务器获取数据
    }
}
xhr.send()
// fetch
fetch(url)
    .then(response => {
        if (response.ok) {
            return response.json();
        }
    })
    .then(data => console.log(data))
    .catch(err => console.log(err))

axios

我们可以为 axios 处理一下错误

axios.get('/user/12345')
    .catch(function(error) {
        if (error.response) {
            // 请求已发出,但服务器响应的状态码不在 2xx 范围内
            console.log(error.response.data);
            console.log(error.response.status);
            console.log(error.response.headers);
        } else {
            // Something happened in setting up the request that triggered an Error
            console.log('Error', error.message);
        }
        console.log(error.config);
    });

取消选择

// 创建取消令牌的生成器对象
var CancelToken = axios.CancelToken;
// 从中获取令牌对象
var source = CancelToken.source();

// 发请求
axios.get('/user/12345', {
    // 传递令牌
    cancelToken: source.token
}).catch(function(thrown) {
    if (axios.isCancel(thrown)) {
        console.log('Request canceled', thrown.message);
    } else {
        // 处理错误
    }
});

// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');

封装完成的取消请求

    import axios from 'axios';

    axios.defaults.timeout = 5000;
    axios.defaults.baseURL = '';

    let pending = []; //声明一个数组用于存储每个ajax请求的取消函数和ajax标识
    let cancelToken = axios.CancelToken;
    let removePending = (ever) => {
        for (let p in pending) {
            if (pending[p].u === ever.url + '&' + ever.method) { //当当前请求在数组中存在时执行函数体
                pending[p].f(); //执行取消操作
                pending.splice(p, 1); //把这条记录从数组中移除
            }
        }
    }

    //http request 拦截器
    axios.interceptors.request.use(
        config => {
            config.data = JSON.stringify(config.data);
            config.headers = {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
            // ------------------------------------------------------------------------------------
            removePending(config); //在一个ajax发送前执行一下取消操作
            config.cancelToken = new cancelToken((c) => {
                // 这里的ajax标识我是用请求地址&请求方式拼接的字符串,当然你可以选择其他的一些方式
                pending.push({
                    u: config.url + '&' + config.method,
                    f: c
                });
            });
            // -----------------------------------------------------------------------------------------
            return config;
        },
        error => {
            return Promise.reject(err);
        }
    );
    //http response 拦截器
    axios.interceptors.response.use(
        response => {
            // ------------------------------------------------------------------------------------------
            removePending(res.config); //在一个ajax响应后再执行一下取消操作,把已经完成的请求从pending中移除
            // -------------------------------------------------------------------------------------------
            if (response.data.errCode == 2) {
                router.push({
                    path: "/login",
                    query: {
                        redirect: router.currentRoute.fullPath
                    } //从哪个页面跳转
                })
            }
            return response;
        },
        error => {
            return Promise.reject(error)
        }
    )

// 自己实现一个简略版的

    // 创建Promise,返回放行开关cancel
    function source() {
        var cancel;
        var promise = new Promise(function(resolve) {
            cancel = resolve;
        });
        return {
            cancel: cancel,
            token: promise
        }
    }
    // 发请求
    function axios_get(config) {
        if (config.cancelToken) {
            config.cancelToken.then(function() {
                xhr.abort();
            })
        }


      // 最终发请求
      xhr.request();
    }

    // 代码执行
    var source = source();

    axios_get({
        cancelToken: source.token
    });

    setTimeout(function() {
        source.cancel(); // 5秒之后执行下一步操作
    }, 5000)

demo

    <button @click="getMsg" class="get-msg">获取数据</button>
    getMsg() {
      let CancelToken = axios.CancelToken;
      let self = this;  
      axios
        .get("http://www.zhangxinxu.com/study/201802/cros-ajax.php", {
          cancelToken: new CancelToken(function executor(c) {
            self.cancel = c;
            console.log(c);
            // 这个参数 c 就是CancelToken构造函数里面自带的取消请求的函数,这里把该函数当参数用
          }),
        })
        .then((res) => {
          this.items = res.data;
        })
        .catch((err) => {
          console.log(err);
        });

      //手速够快就不用写这个定时器了,点击取消获取就可以看到效果了
      setTimeout(function () {
        //只要我们去调用了这个cancel()方法,没有完成请求的接口便会停止请求
        self.cancel();
      }, 100);
    }

最后更新于