js之网络请求和Axios使用及原理。

292 阅读6分钟

异步请求(ajax):发送服务器请求额外数据而不刷新页面,从而实现更好的用户体验。

XMLHttpRequest

XMLHttpRequest对象:为发送服务器请求和获取响应提供了合理的接口。这个接口可以实现异步从服务器获取额外数据,用户点击不同刷新页面页可以获取数据。

简单使用

var xhr = new XMLHttpRequest()
xhr.onreadyStateChange = function () {
  if (xhr.readystate === 4) {
    if (xhr.status === 304 || (xhr.status >= 200 && xhr.status < 300)) {
      console.log('type: success, result: ', xhr.responseText)
    } else {
      console.log('type: error, errCode:', xhr.status)
    }
  }
}
xhr.open('get', 'example.php', true)
xhr.setRequestHeader('testHeader', 'testHeaderVal')
xhr.send(null)

xhr属性

属性描述
readyState返回xhr的状态
0:xhr对象一创建,但尚未调用open()方法
1:open()方法已经被调用
2:send()方法已经被调用,并且头部和状态已经可获得
3:请求中,responseText属性已经包含部分属性
4:请求已完成
response返回响应的正文
responseText返回文本
responseType用于指定响应中包含的数据类型:
xhr.responseType = 'json'/'text'/'document'
responseURL获取返回响应的序列化URL
responseXML返回一个包含请求检索的html或xml的Document
status返回响应的数值状态码:
status状态码与http状态码相同
statusText返回响应状态的文本信息。
timeout设置请求的超时时间,
upload

xhr方法

方法描述
open(method,url,isAsync)初始化一个新创建的请求
setRequestHeader(header,value)设置请求头
send()用于发送http请求
接受一个参数做为请求体
如果请求方法为get和head,应将请求体设置为null
abort()xhr.abort():终止请求
getAllResponseHeaders()获取所有响应头
getAllResponseHeader()获取单个响应头的值

xhr事件

事件描述
onreadystatechange当readystate状态改变时触发
ontimeout响应超时时触发
onabort请求终止时触发
onerror请求错误时触发
onloadstart接收到第一个响应开始触发
onload请求完成时触发
onprogress请求中周期性触发

Axios

axios是一个基于promise的网络请求库,可以用于浏览器和node.js

axios的特点

  1. 基于promise的异步ajax请求库
  2. 浏览器端和node都可以使用
  3. 支持请求、响应拦截器
  4. 支持请求取消
  5. 请求和响应的数据转换
  6. 支持发送多个请求

axios的使用

axios(config): 通用/最本质的发任意类型请求的方式

axios.request(config): 等同于 axios(config)

axios.get(url[, config]): 发 get 请求

axios.delete(url[, config]): 发 delete 请求

axios.post(url[, data, config]): 发 post 请求

axios.put(url[, data, config]): 发 put 请求

axios.defaults.xxx: 请求的默认全局配置

axios.interceptors.request.use(): 添加请求拦截器

axios.interceptors.response.use(): 添加响应拦截

axios.create([config]): 创建一个新的 axios

axios.Cancel(): 用于创建取消请求的错误对象

axios.CancelToken(): 用于创建取消请求的 token 对象

axios.isCancel(): 是否是一个取消请求的错误

axios.all(promises): 用于批量执行多个异步请求

axios.spread(): 用来指定接收所有成功数据的回调函数的方法

具体使用详情,请参考:github.com/axios/axios…

拦截器使用

// 请求拦截器
axios.interceptors.request.use(function (config) {
    // 发送请求之前的配置
    return config;
  }, function (error) {
    // 发送请求失败之后的操作
    return Promise.reject(error);
  });

// 响应拦截器
axios.interceptors.response.use(function (response) {
    // 请求成功之后,对响应数据进行操作
    return response;
  }, function (error) {
    // 失败之后的操作
    return Promise.reject(error);
  });

注意:请求拦截器后定义的会先执行,具体原因请看拦截器内部实现原理。

拦截器功能实现

拦截器:拦截器本质对象,对象里面存放是一个数组,里面存放一个对象(fulfilled,rejected)对象,分别表示promise成功和失败的回调;

拦截器实例对象上包含的方法:

  1. use:添加拦截器
  2. eject:删除拦截器
function InterceptorManager(){
    this.handlers = [] // 保存拦截器
}
// 定义use 添加拦截器的方法
InterceptorManager.prototype.use = functioon(fulfilled,rejected){
    this.handlers.push({
        fulfilled,
        rejected
    })
    return this.handlers.length - 1;
}
// 定义删除拦截器的方法
InterceptorManager.prototype.eject = functioon(id){
    if(this.handlers[id])  this.handlers[id] = null;
}

Axios类功能实现

Axios类主要用于:

  1. 设置发送请求的配置

  2. 设置拦截器

  3. 设置请求方法request

    创建请求链,本质是一个数组,有请求拦截器,请求方法,响应拦截器方法组成

    通过promise链调用请求链

    最后返回promise

  4. 在原型对象上封装get,post等请求方法

const dispatchRequest = "请求方法" // 可能是xhr或者http

function Axios(defaults){
    this.defaults = defaults;// 设置默认配置
    this.interceptors = {    // 设置拦截器
        request:new InterceptorManager(),
        response:new InterceptorManager()
    }
}

// 在原型对象上挂载原始请求方法 
Axios.prototype.request = function requset(config){
    // 合并配置,暂时不用关注,配置主要用于真实发送请求的文件中
    config = mergeConfig(this.defaults,config);
    
    // 创建拦截器中间键,我称之为请求链,undefined主要起到占位的作用,因为定义拦截器是方法都是成对出现的。
    let chain = [dispatcRequest,undefined];
    // 将请求拦截器加到请求链的前面,因为请求拦截器是往数组头部添加元素所以后定义在会先执行。
    this.interceptors.request.forEach(item=>{
        chain.unshift(item.fuifilled,item,rejected);
    })
    // 将响应拦截器加到请求链的后面
    this.interceptors.request.forEach(item=>{
        chain.push(item.fuifilled,item,rejected);
    })
    // 创建一个成功的promise,并且设置成功的值为config对象(设置promise起点)
    let promise = Promise.resolve(config);
    
    // 开始指向chain
    while(chain.length > 0){
        promise = promise.then(chain.shift(),chain.shift());
    }
    // 最后返回promise对象
    return promise;
}

axios对象的实现

axios对Axios类再次进行封装,是用户可以更加灵活使用。

  1. 创建Axios对象context
  2. 将Axios实例对象上的request方法,绑定到context上并返回型函数instance
  3. 将Axios实例对象上的方法绑定到instance上。
  4. 将context对象上的属性绑定到instance上
function createInstance(defaults){
    const context = new Axios(defaults);
    
    const instance = context.prototype.request.bind(context);// 将request方法返回,内部this指向context
    for(let key in Axios.prototype){
        instance[key] = Axios.prototype[key].bind(context);
    }
    for(let key in context){
        instance[key] = conetxt[key];
    }
    return instance;
}

// axio是函数,但也是对象上面有get,post,requset等方法和拦截器属性等
const axios = createInstance(defaults); // 此时返回的axios就是请求库暴露出的唯一接口axios
// axios添加属性Axios构造函数
axios.Axios = Axios;
// 添加一个工厂函数,用于返回不同配置的axios对象
axios.create = function(config){
    return createInstance(mergeConfig(default,config));
}

// 添加可取消选项
axios.Cancel
axios.CancelToken
axios.isCancel

// 用于并发请求
axios.all = function all(promises){
    return Promise.all(promise);
}
axios.spread = function spread(cb){
    return function(arr){
        return cb.apply(null,arr);
    }
}

axios取消请求使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>取消请求</title>
    <link crossorigin='anonymous' href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <script src="./node_modules/axios/dist/mine-axios.js"></script>
</head>
<body>
    <div class="container">
        <h2 class="page-header">axios取消请求</h2>
        <button class="btn btn-primary"> 发送请求 </button>
        <button class="btn btn-warning"> 取消请求 </button>
    </div>
    <script>
        //获取按钮
        const btns = document.querySelectorAll('button');
        //2.声明全局变量
        let cancel = null;
        //发送请求
        btns[0].onclick = function(){
            //检测上一次的请求是否已经完成
            if(cancel !== null){
                //取消上一次的请求
                cancel();
            }

            //创建 cancelToken 的值
            let cancelToken = new axios.CancelToken(
                function(c){
                    //3. 将 c 的值赋值给 cancel
                    cancel = c;
                });

            axios({
                method: 'GET',
                url: 'http://localhost:3000/posts',
                //1. 添加配置对象的属性
                cancelToken: cancelToken
            }).then(response => {
                console.log(response);
                //将 cancel 的值初始化
                cancel = null;
            })
        }

        //绑定第二个事件取消请求
        btns[1].onclick = function(){
            cancel();
        }
    </script>   
</body>
</html>

axios取消请求使用

  1. 通过CancelToken.source工厂函数

    const CancelToken = axios.CancelToken;
    const source = CancelToken.source();
    
    axios.get('/user/12345', {
      cancelToken: source.token
    }).catch(function (thrown) {
      if (axios.isCancel(thrown)) {
        console.log('Request canceled', thrown.message);
      } else {
        // handle error
      }
    });
    
    axios.post('/user/12345', {
      name: 'new name'
    }, {
      cancelToken: source.token
    })
    
    // cancel the request (the message parameter is optional)
    source.cancel('Operation canceled by the user.');
    
  2. 通过回调函数

    const CancelToken = axios.CancelToken;
    let cancel;
    
    axios.get('/user/12345', {
      cancelToken: new CancelToken(function executor(c) {
        // An executor function receives a cancel function as a parameter
        cancel = c;
      })
    });
    
    // cancel the request
    cancel();
    

axios是如何实现取消请求

// 请求函数
function xhrAdapter(config){
    //发送 AJAX 请求
    return new Promise((resolve, reject) => {
        //实例化对象
        const xhr = new XMLHttpRequest();
        //初始化
        xhr.open(config.method, config.url);
        //发送
        xhr.send();
        //处理结果
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4){
                //判断结果
                if(xhr.status >= 200 && xhr.status < 300){
                    //设置为成功的状态
                    resolve({
                        status: xhr.status,
                        statusText: xhr.statusText
                    });
                }else{
                    reject(new Error('请求失败'));
                }
            }
        }
        //关于取消请求的处理
        if(config.cancelToken){
            //对 cancelToken 对象身上的 promise 对象指定成功的回调
            config.cancelToken.promise.then(value => {
                xhr.abort();
                //将整体结果设置为失败
                reject(new Error('请求已经被取消'))
            });
        }
    })
}


// 取消请求函数
//CancelToken 构造函数
function CancelToken(executor){
    //声明一个变量
    var resolvePromise;
    //为实例对象添加属性
    this.promise = new Promise((resolve) => {
        //将 resolve 赋值给 resolvePromise
        resolvePromise = resolve
    });
    //调用 executor 函数
    executor(function(){
        //执行 resolvePromise 函数
        resolvePromise();
    });
}

// 工厂函数。
CancelTOken.source = function source(){
    let cancel; // 定义取消函数
    let token = new CancelToken(function(c){
        cancel = c;
    })
    return {
        token:token,
        cancel:cancel
    }
}