参考axios和jquery进行ajax封装

网上虽然有很多ajax的封装,但都或多或少的存在一些问题,比如没有封装取消请求的方法,也没有对请求参数的对象属性是数组或对象的处理。
以jquery为例,这个请求的数据就比较复杂

  $.ajax({
      type:'get',
      data:{
          name:'张三',
          age:18,
          url:'http://172.31.14.33:3000',
          info:{
              hobbies:['唱','跳','rap','篮球'],
              score:{
                  english:79,
                  math:90,
              }
          }
      }
  })

jquery处理后的url是这样的:


企业微信截图_20221031182317.png
企业微信截图_20221031182327.png

axios的处理是这样的:


企业微信截图_20221031183112.png
企业微信截图_20221031183122.png

最后我采用了axios的封装形式。

Promise形式的封装(使用方式类似于axios)

代码:

//my_ajax_promise.js
let ajax = function (options) {
    //步骤:
    //1.创建异步请求对象
    //2.创建状态监听函数
    //3.设置请求方式和地址
    //4.发送请求
    //5.接收返回的数据
    return new Promise((resolve, reject) => {
        //1.创建异步请求对象
        let xhr = new XMLHttpRequest() || new ActiveXObject('Mricosoft.XMLHTTP');//兼容IE6及以下
        //设置超时时间
        if (options.timeout > 0) {
            xhr.timeout = options.timeout;
        }
        //处理请求方式
        if (options.method) {
            options.method = options.method.toUpperCase();//转成大写
        } else {
            options.method = 'GET';//默认为get请求
        }

        //2.创建事件监听函数,如果在open之后才创建监听函数,readyState就已经变成2了
        //如果根据xhr.readyState去处理的话不好区别错误类型,这里全部使用事件进行处理
        bindEvent(xhr, resolve, reject, options);

        //3.设置请求方式和地址
        if (options.method === 'POST') {//post请求
            //设置请求方式,请求地址和是否异步
            xhr.open('POST', options.url, options.async ? options.async : true);
            //自定义请求头(open之后才能设置请求头)
            setRequestHeader(xhr, options);
            //4.发送请求
            if (options.processData !== false) {//post请求时数据是否需要转字符串,默认要(FormData等数据不用转)
                options.processData = true;
            }
            if (options.processData) {//是否需要转成JSON字符串
                xhr.send(JSON.stringify(options.data));
            } else {
                xhr.send(options.data);
            }
        } else {//get请求
            xhr.open('GET', options.url + '?' + paramsToUrl(options.data), options.async ? options.async : true);
            setRequestHeader(xhr, options);
            xhr.send();
        }
        //取消请求
        if (options.cancelToken) {
            let executor = options.cancelToken;
            executor(xhr.abort.bind(xhr));//执行executor函数并传参,让abort的this始终指向其异步对象,避免abort在被调用时其this指向被改变
        }
    })
}

//CancelToken的实例对象返回executor函数
ajax.CancelToken = function (executor) {
    return executor;
}

//绑定事件
function bindEvent(xhr, resolve, reject, options) {
    //开始接收事件,在接收到响应数据的第一个字节时触发
    xhr.onloadstart = function (e) {}
    //上传事件,有文件上传时触发
    if (xhr.upload) {
        xhr.upload.onprogress = function (e) {
            if (typeof options.onUploadProgress === 'function') {
                try {
                    options.onUploadProgress(e);
                } catch (err) {
                    reject(error('TypeError', err,xhr));
                    throw new TypeError(err);
                }
            }
        }
    }
    //接收进度事件,在请求接收到数据的时候被周期性触发
    xhr.onprogress = function (e) {
        if (typeof options.onDownloadProgress === 'function') {
            try {
                options.onDownloadProgress(e);
            } catch (err) {
                reject(error('TypeError', err,xhr));
                throw new TypeError(err);
            }
        }
    }
    //超时事件,请求超时时触发
    xhr.ontimeout = function (e) {
        reject(error(e.type, 'network timeout',xhr));
    }
    //取消事件,取消请求时触发
    xhr.onabort = function (e) {
        reject(error(e.type, 'upload canceled',xhr));
    }
    //错误事件,在请求发生错误时触发
    xhr.onerror = function (err) {
        reject(error(err.type, 'network error',xhr));
    }
    //请求完成的时候会触发load事件
    xhr.onload = function (e) {
        if (xhr.status >= 200 && xhr.status < 300) {
            resolve(success(xhr));
        } else {
            reject(error('error', 'network error',xhr));
        }
    }
    //请求完成事件,loadend事件总是在一个资源的加载进度停止之后被触发(error、abort或load都能触发)
    xhr.onloadend = function (e) {
        //兜底处理(按理来说不会走到这里,万一呢)
        if (xhr.status >= 200 && xhr.status < 300) {
            resolve(success(xhr));
        } else {
            reject(error('error', 'network error',xhr));
        }
    }
}

//设置请求头
function setRequestHeader(xhr, options) {
    //跨域请求时是否需要使用cookie凭证(默认不需要)
    if(options.withCredentials){
        xhr.withCredentials=true;
    }
    if (options.headers) {
        for (const key in options.headers) {
            if (options.headers.hasOwnProperty(key)) {
                xhr.setRequestHeader(key, options.headers[key]);
            }
        }
        //没设置Content-Type时默认为JSON,上传文件时可设置contentType为false,让浏览器根据数据类型自动设置即可
        if (!options.headers['Content-Type'] && options.contentType !== false) {
            xhr.setRequestHeader('Content-Type', 'application/json');//默认数据格式为JSON
        }
    } else if (options.contentType !== false) {
        xhr.setRequestHeader('Content-Type', 'application/json');//默认数据格式为JSON
    }
}

//将get请求数据转成url参数(参数处理格式参考了axios和jquery对get请求参数的处理)
function paramsToUrl(params) {
    let _result = [];
    for (let key in params) {
        if (params.hasOwnProperty(key)) {
            let value = params[key]
            // 去掉为空的参数
            if (['', undefined, null].includes(value)) {
                continue;
            }
            if (Array.isArray(value)) {//数组处理
                arrayDeal(_result, key, value);
            } else if (Object.prototype.toString.call(value) === '[object Object]') {//对象处理
                objDeal(_result, key, value);
            } else {
                _result.push(key + '=' + encodeURIComponent(value));
            }
        }
    }

    return _result.length ? _result.join('&') : ''
}

//数组遍历并处理
function arrayDeal(result, attributeName, array) {
    array.forEach(function (item, index) {
        if (Array.isArray(item)) {//数组元素是数组的话需要进行遍历
            arrayDeal(result, attributeName + '[' + index + ']', item);
        } else if (Object.prototype.toString.call(item) === '[object Object]') {//数组元素是对象的话需要进行处理
            objDeal(result, attributeName + '[' + index + ']', item);
        } else {
            result.push(attributeName + '[' + index + ']=' + encodeURIComponent(item));
        }
    })
}

//对象属性遍历并处理
function objDeal(result, attributeName, obj) {
    for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (Array.isArray(obj[key])) {//对象属性值为数组的时候需要进行处理
                arrayDeal(result, attributeName + '[' + key + ']', obj[key]);
            } else if (Object.prototype.toString.call(obj[key]) === '[object Object]') {//对象属性值为对象时需要进行处理
                objDeal(result, attributeName + '[' + key + ']', obj[key]);
            } else {
                result.push(attributeName + '[' + key + ']=' + encodeURIComponent(obj[key]));
            }
        }
    }
}

//错误对象
function error(type, msg,xhr) {
    return {
        type: type || '',
        errorMsg: msg || '',
        request:xhr,
    }
}

//成功对象
function success(xhr) {
    let responseData = {
        // `data` 由服务器提供的响应
        data: {},
        // `status` 来自服务器响应的 HTTP 状态码
        status: xhr.status,
        // `statusText` 来自服务器响应的 HTTP 状态信息
        statusText: xhr.statusText,
        // `headers` 服务器响应的头
        headers: xhr.getAllResponseHeaders(),
        request: xhr,
    }
    try {
        //接口返回的数据是json字符串时
        responseData.data = JSON.parse(xhr.response);
        return responseData;
    } catch (e) {
        responseData.data = xhr.response;
        return responseData;
    }
}

export default ajax;

基本的使用方式:

import ajax from "./my_ajax_promise.js";

let CancelToken = ajax.CancelToken;
let cancel;//接收取消请求
ajax({
    method: '',
    url: '',
    data: {},
    onUploadProgress: function (e) {
        console.log(e);
    },
    onDownloadProgress: function (e) {
        console.log(e);
    },
    cancelToken: new CancelToken(function executor(c) {
        // executor的参数c就是取消请求的方法
        cancel = c;//调用cancel即可取消请求
    })
}).then(res => {
   //成功逻辑
}).catch(err => {
    //失败逻辑
});

回调形式的封装(使用方式类似于jquery的ajax)

代码:

//my_ajax_callback.js
let ajax = function (options) {
  //步骤:
  //1.创建异步请求对象
  //2.创建状态监听函数
  //3.设置请求方式和地址
  //4.发送请求
  //5.接收返回的数据
    //1.创建异步请求对象
    let xhr = new XMLHttpRequest() || new ActiveXObject('Mricosoft.XMLHTTP');//兼容IE6及以下
    xhr.isEnd=false;//判断请求是否已经被处理完毕,避免重复触发事件
    //设置超时时间
    if (options.timeout > 0) {
      xhr.timeout = options.timeout;
    }
    //处理请求方式
    if (options.method) {
      options.method = options.method.toUpperCase();//转成大写
    } else {
      options.method = 'GET';//默认为get请求
    }

    //2.创建事件监听函数,如果在open之后才创建监听函数,readyState就已经变成2了
    //如果根据xhr.readyState去处理的话不好区别错误类型,这里全部使用事件进行处理
    bindEvent(xhr, options);

    //3.设置请求方式和地址
    if (options.method === 'POST') {//post请求
      //设置请求方式,请求地址和是否异步
      xhr.open('POST', options.url, options.async ? options.async : true);
      //自定义请求头(open之后才能设置请求头)
      setRequestHeader(xhr, options);
      //4.发送请求
      if (options.processData !== false) {//post请求时数据是否需要转字符串,默认要(FormData等数据不用转)
        options.processData = true;
      }
      if (options.processData) {//是否需要转成JSON字符串
        xhr.send(JSON.stringify(options.data));
      } else {
        xhr.send(options.data);
      }
    } else {//get请求
      xhr.open('GET', options.url + '?' + paramsToUrl(options.data), options.async ? options.async : true);
      setRequestHeader(xhr, options);
      xhr.send();
    }
    //将xhr对象返回出去,便于在外部调用其方法(例如abort方法)
    return xhr;
}


//绑定事件
function bindEvent(xhr, options) {
  //开始接收事件,在接收到响应数据的第一个字节时触发
  xhr.onloadstart = function (e) {}
  //上传事件,有文件上传时触发
  if (xhr.upload) {
    xhr.upload.onprogress = function (e) {
      if (typeof options.onUploadProgress === 'function') {
        try {
          options.onUploadProgress(e);
        } catch (err) {
          if(typeof options.error==='function'){
            options.error(error('TypeError', err,xhr))
          }
          throw new TypeError(err);
        }
      }
    }
  }
  //接收进度事件,在请求接收到数据的时候被周期性触发
  xhr.onprogress = function (e) {
    if (typeof options.onDownloadProgress === 'function') {
      try {
        options.onDownloadProgress(e);
      } catch (err) {
        if(typeof options.error==='function'){
          options.error(error('TypeError', err,xhr))
        }
        throw new TypeError(err);
      }
    }
  }
  //超时事件,请求超时时触发
  xhr.ontimeout = function (e) {
    if(xhr.isEnd){
      return;
    }else {
      xhr.isEnd=true;
    }
    if(typeof options.error==='function'){
      options.error(error(e.type, 'network timeout',xhr));
    }
  }
  //取消事件,取消请求时触发
  xhr.onabort = function (e) {
    if(xhr.isEnd){
      return;
    }else {
      xhr.isEnd=true;
    }
    if(typeof options.error==='function'){
      options.error(error(e.type, 'upload canceled',xhr));
    }
  }
  //错误事件,在请求发生错误时触发
  xhr.onerror = function (err) {
    if(xhr.isEnd){
      return;
    }else {
      xhr.isEnd=true;
    }
    if(typeof options.error==='function'){
      options.error(error(err.type, 'network error',xhr));
    }
  }
  //请求完成的时候会触发load事件
  xhr.onload = function (e) {
    if(xhr.isEnd){
      return;
    }else {
      xhr.isEnd=true;
    }
    if (xhr.status >= 200 && xhr.status < 300) {
      if(typeof options.success==='function'){
        options.success(success(xhr))
      }
    } else {
      if(typeof options.error==='function'){
        options.error(error('error', 'network error',xhr));
      }
    }
  }
  //请求完成事件,loadend事件总是在一个资源的加载进度停止之后被触发(error、abort或load都能触发)
  xhr.onloadend = function (e) {
    if(xhr.isEnd){
      return;
    }else {
      xhr.isEnd=true;
    }
    //兜底处理(按理来说不会走到这里,万一呢)
    if (xhr.status >= 200 && xhr.status < 300) {
      if(typeof options.success==='function'){
        options.success(success(xhr));
      }
    } else {
      if(typeof options.error==='function'){
        options.error(error('error', 'network error',xhr))
      }
    }
  }
}

//设置请求头
function setRequestHeader(xhr, options) {
  //跨域请求时是否需要使用cookie凭证(默认不需要)
  if(options.withCredentials){
    xhr.withCredentials=true;
  }
  if (options.headers) {
    for (const key in options.headers) {
      if (options.headers.hasOwnProperty(key)) {
        xhr.setRequestHeader(key, options.headers[key]);
      }
    }
    //没设置Content-Type时默认为JSON,上传文件时可设置contentType为false,让浏览器根据数据类型自动设置即可
    if (!options.headers['Content-Type'] && options.contentType !== false) {
      xhr.setRequestHeader('Content-Type', 'application/json');//默认数据格式为JSON
    }
  } else if (options.contentType !== false) {
    xhr.setRequestHeader('Content-Type', 'application/json');//默认数据格式为JSON
  }
}

//将get请求数据转成url参数(参数处理格式参考了axios和jquery对get请求参数的处理)
function paramsToUrl(params) {
  let _result = [];
  for (let key in params) {
    if (params.hasOwnProperty(key)) {
      let value = params[key]
      // 去掉为空的参数
      if (['', undefined, null].includes(value)) {
        continue;
      }
      if (Array.isArray(value)) {//数组处理
        arrayDeal(_result, key, value);
      } else if (Object.prototype.toString.call(value) === '[object Object]') {//对象处理
        objDeal(_result, key, value);
      } else {
        _result.push(key + '=' + encodeURIComponent(value));
      }
    }
  }

  return _result.length ? _result.join('&') : ''
}

//数组遍历并处理
function arrayDeal(result, attributeName, array) {
  array.forEach(function (item, index) {
    if (Array.isArray(item)) {//数组元素是数组的话需要进行遍历
      arrayDeal(result, attributeName + '[' + index + ']', item);
    } else if (Object.prototype.toString.call(item) === '[object Object]') {//数组元素是对象的话需要进行处理
      objDeal(result, attributeName + '[' + index + ']', item);
    } else {
      result.push(attributeName + '[' + index + ']=' + encodeURIComponent(item));
    }
  })
}

//对象属性遍历并处理
function objDeal(result, attributeName, obj) {
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      if (Array.isArray(obj[key])) {//对象属性值为数组的时候需要进行处理
        arrayDeal(result, attributeName + '[' + key + ']', obj[key]);
      } else if (Object.prototype.toString.call(obj[key]) === '[object Object]') {//对象属性值为对象时需要进行处理
        objDeal(result, attributeName + '[' + key + ']', obj[key]);
      } else {
        result.push(attributeName + '[' + key + ']=' + encodeURIComponent(obj[key]));
      }
    }
  }
}

//错误对象
function error(type, msg,xhr) {
  return {
    type: type || '',
    errorMsg: msg || '',
    request:xhr,
  }
}

//成功对象
function success(xhr) {
  let responseData = {
    // `data` 由服务器提供的响应
    data: {},
    // `status` 来自服务器响应的 HTTP 状态码
    status: xhr.status,
    // `statusText` 来自服务器响应的 HTTP 状态信息
    statusText: xhr.statusText,
    // `headers` 服务器响应的头
    headers: xhr.getAllResponseHeaders(),
    request: xhr,
  }
  try {
    //接口返回的数据是json字符串时
    responseData.data = JSON.parse(xhr.response);
    return responseData;
  } catch (e) {
    responseData.data = xhr.response;
    return responseData;
  }
}

基本的使用方式:

import ajax from "./my_ajax_callback.js";

let xhr=ajax({
    method:'post',
    url:'',
    data:{},
    success:function (res) {
        //处理成功请求
        console.log(res);
    },
    error:function (err) {
        //处理失败请求
        console.log(err);
    }
})

//xhr.abort();//取消请求

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容