zhr 一个简易的fetch封装

/**
 * 一个简易fetch封装
 * author: slongzhang
 * date: 2022-06-13
 * 修改于 https://github.com/Sunny-lucking/howToBuildMyAxios
 */ 
( function( global, factory ) {
  "use strict";
  if ( typeof module === "object" && typeof module.exports === "object" ) {

    // For CommonJS and CommonJS-like environments where a proper `window`
    // is present, execute the factory and get zhr.
    // For environments that do not have a `window` with a `document`
    // (such as Node.js), expose a factory as module.exports.
    // This accentuates the need for the creation of a real `window`.
    // e.g. var zhr = require("zhr")(window);
    // See ticket #14549 for more info.
    module.exports = global.document ?
      factory( global, true ) :
      function( w ) {
        if ( !w.document ) {
          throw new Error( "zhr requires a window with a document" );
        }
        return factory( w );
      };
  } else {
    factory( global );
  }

// Pass this if window is not defined yet
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
  "use strict"; 

// 可用请求方法
const methodsArr = ['get', 'head', 'post', 'put', 'delete', 'connect', 'options', 'trace', 'patch'];

// 判断是否为空
const empty = (value) => {
  if (null === value) {
    return true;
  } else if (['Set', 'Map'].includes(Object.prototype.toString.call(value).slice(8, -1))) {
    return value.size == 0 ? true : false;
  } else if ("object" == typeof value) {
    if (Object.getOwnPropertyDescriptor(value, "length")) {
      return value.length > 0 ? false : true;
    } else {
      return Object.keys(value).length > 0 ? false : true;
    }
  } else if (value == 0) {
    return true;
  } else {
    return !value;
  }
}

// 获取数据类型
const varType = (any) => {
  return Object.prototype.toString.call(any).slice(8, -1).toLowerCase();
}

/**
* trim基础函数
* str              String                要处理的原始字符串
* charlist         String                要去除的字符
* type             Integer               去除类型:0两边,1:左边,2右边
.*/
const trimBase = (str, charlist, type = 0) => {
  if (typeof str !== 'string') {
    return str;
  }
  if (typeof charlist === 'undefined') {
    if (type === 1) {
      return str.replace(/^\s+/gm, '');
    } else if (type === 2) {
      return str.replace(/\s+$/gm, '');
    } else {
      return str.replace(/^\s+|\s+$/gm, '');
    }
  }
  charlist = charlist.toString().split('');
  let zy = ['$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '/', '^', '{', '}', '!', '|'],
    ps = '';
  for (let item of charlist) {
    if (zy.includes(item)) {
      ps += '\\';
    }
    ps += item;
  }
  let reg;
  if (type === 1) {
    reg = new RegExp("\^" + ps + "+", "gm");
  } else if (type === 2) {
    reg = new RegExp(ps + "+$", "gm");
  } else {
    reg = new RegExp("\^" + ps + "+|" + ps + "+$", "gm");
  }
  return str.replace(reg, '');
}






// 工具类
const utils = {
  extend(a, b, context) {
    for (let key in b) {
      if (b.hasOwnProperty(key)) {
        if (typeof b[key] === 'function') {
          a[key] = b[key].bind(context);
        } else {
          a[key] = b[key]
        }
      }

    }
  },
  // 请求参数转换
  transformRequestParam(param, urlEncode = false) {
    let dataType = varType(param),
      result;
    switch (dataType) {
      case 'array': {
        result = [];
        Object.values(param).forEach((item, index) => {
          switch (varType(item)) {
            case 'string':
              result.push(item);
              break;
            case 'array':
              item[1] = empty(item[1]) ? '' : item[1];
              !empty(item[0]) && result.push(item[0] + '=' + encodeURIComponent(item[1]));
              break;
            case 'object':
              item.value = empty(item.value) ? '' : item.value;
              !empty(item.key) && result.push(item.key + '=' + encodeURIComponent(item.value));
              break;
          }
        })
        result = result.join('&');
      }
      break;
      case 'object': {
        result = [];
        Object.entries(param).forEach(([key, item = ''], index) => {
          result.push(key + '=' + encodeURIComponent(item));
        })
        result = result.join('&');
        break;
      }
      default:
        result = (urlEncode && varType(result) !== 'string') ? '' : param;
    }
    return result;
  },
  // 移除两侧字符串
  trim(str, charlist) {
    return trimBase(str, charlist, 0);
  }
  // 移除左侧字符
  ,
  ltrim(str, charlist) {
    return trimBase(str, charlist, 1);
  }
  // 移除右侧字符
  ,
  rtrim(str, charlist) {
    return trimBase(str, charlist, 2);
  }
  // 判断是否数字
  ,
  isNum(value) {
    return value !== "" && value !== null && !isNaN(value);
  }
}


// 拦截器
class InterceptorsManage {
  constructor() {
    this.handlers = [];
  }

  use(fullfield, rejected) {
    this.handlers.push({
      fullfield,
      rejected
    })
  }
}


// 发送请求
const sendXhr = (config) => {
  return new Promise((resolve, reject) => {
    let timeoutId = null, headers;
    if (varType(config.headers) !== 'object') {
      headers = {}
    }
    // if (empty(headers.accept)) {
    //   headers.accept = 'application/json, text/javascript, */*; q=0.01';
    // }
    // 判断url
    let url = varType(config.url) === 'string' ? config.url : '';
    // 判断方法
    let method = (varType(config.method) === 'string' &&
      (config.method = config.method.toLowerCase()) &&
      methodsArr.includes(config.method)) ? config.method : 'GET';
    method = method.toUpperCase(); // 转换为大写
    // 处理body
    if (!empty(config.body)) {
      config.body = utils.transformRequestParam(config.body, method === 'GET');
    }
    switch (varType(config.body)) {
      case 'formdata':
        headers['content-type'] = 'application/multipart/form-data';
        break;
      case 'object':
        config.body = JSON.stringify(config.body);
        headers['content-type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
        break;
      default:
        try {
          JSON.parse(config.body);
          headers['content-type'] = 'application/json; charset=UTF-8';
        } catch (e) {
          headers['content-type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
        }
    }

    // 处理query
    let query = '';
    if (!empty(config.query)) {
      query = utils.transformRequestParam(config.query, true);
      // url = strstr($url,'?')? utils.trim($url,'&').'&'.$param: $url.'?'.$param;
    }
    // 如果是get|delete|...等请求则将body内容处理到url里
    if (['GET', 'DELETE', 'HEAD', 'OPTIONS'].includes(method) && !empty(config.body) && varType(config.body) === 'string') {
      query = utils.trim(query + '&' + config.body, '&');
    }
    // 如果有query
    if (!empty(query)) {
      url = url.indexOf('?') > 0 ? utils.trim(url, '&') + '&' + query : url + '?' + query;
    }
    

    

    // // 判断是否异步请求
    // if (!!config.async) {

    // }
    // else {

    // }
    // console.log({config})
    // 重新规范配置
    let requestConfig = {
      url,
      method,
      headers,
      cache: config.cache,
      credentials: 'include',
    }
    if (!['GET', 'DELETE', 'HEAD', 'OPTIONS'].includes(method)) {
      requestConfig.body = config.body;
    }
    if (varType(config.xhrFields) === 'object') {
      Object.entries(config.xhrFields).forEach(([key, item], index) => {
        if (key === 'headers') {
          if (varType(item) === 'object') {
            Object.entries(item).forEach(([idx, val], ii) => {
              requestConfig.headers[idx] = val;
            })
          }
        } else if (!['url', 'method'].includes(key)) {
          requestConfig[key] = item;
        }
      })
    }
    // 处理超时
    if (config.timeout != 0 && config.timeout !== false) {
      const controller = new AbortController()
      if (utils.isNum(config.timeout)) {
        config.timeout = parseInt(config.timeout)
      }
      else {
        config.timeout = 60000;// 60 second timeout
      }
      timeoutId = setTimeout(() => controller.abort(), config.timeout)
      requestConfig.signal = controller.signal;
    }
    // 正式请求获取
    fetch(url, requestConfig).then(response => {
      let responseType = null,
        responseHeadersArray = [],
        responseHeaders = {},
        responsePromise;
      response.headers.forEach((item, key) => {
        responseHeadersArray.push({
          key,
          item
        });
        responseHeaders[key] = item;
      })
      // 判断有没有指定获取的数据类型
      if (varType(config.responseType) === 'string' && ['json', 'text', 'blob', 'arraybuffer'].includes(config.responseType.toLowerCase())) {
        responseType = config.responseType.toLowerCase();
      } else {
        // 从响应格式里获取
        responseType = response.headers.get('content-type');
        if (responseType.includes('/json')) {
          responseType = 'json';
        } else if (responseType.includes('text/html')) {
          responseType = 'text';
        }
      }
      switch (responseType) {
        case 'json':
          responsePromise = response.json()
          break;
        case 'blob':
          responsePromise = response.blob()
          break;
        case 'arraybuffer':
          responsePromise = response.arrayBuffer()
          break;
        default:
          responsePromise = response.text()
      }
      responsePromise.then(result => {
        clearTimeout(timeoutId) // 清除请求超时
        response.$result = result;
        response.$config = requestConfig;
        response.$responseHeaders = responseHeaders;
        response.$responseHeadersArray = responseHeadersArray;

        resolve(response)
      }).catch(reject);
    }).catch(reject)
  })
}



// 请求方法
class Zhr {
  constructor() {
    this.interceptors = {
      request: new InterceptorsManage,
      response: new InterceptorsManage
    }
  }

  request(config) {
    // 拦截器和请求组装队列
    let chain = [sendXhr.bind(this), undefined] // 成对出现的,失败回调暂时不处理

    // 请求拦截
    this.interceptors.request.handlers.forEach(interceptor => {
      chain.unshift(interceptor.fullfield, interceptor.rejected)
    })

    // 响应拦截
    this.interceptors.response.handlers.forEach(interceptor => {
      chain.push(interceptor.fullfield, interceptor.rejected)
    })

    // 执行队列,每次执行一对,并给promise赋最新的值
    let promise = Promise.resolve(config);
    while (chain.length > 0) {
      promise = promise.then(chain.shift(), chain.shift())
    }
    return promise;
  }
}



// 定义get,post...方法,挂在到Zhr原型上
methodsArr.forEach(met => {
  Zhr.prototype[met] = function() {
    // console.log('执行'+met+'方法');
    return this.request({
      method: met,
      url: arguments[0],
      body: arguments[1] || {},
      ...arguments[2] || {}
    })
  }
})

// 最终导出 zhr 的方法,即实例的request方法
function createZhrFn() {
  let zhr = new Zhr();

    let req = zhr.request.bind(zhr);
    // 混入方法, 处理zhr的request方法,使之拥有get,post...方法
    utils.extend(req, Zhr.prototype, zhr)
    // 混入属性,处理zhr的request方法,使之拥有zhr实例上的所有属性
    utils.extend(req, zhr)
    return req;
}

const zhr = createZhrFn();


// 使用案例

// // 添加请求拦截器
// zhr.interceptors.request.use(function (config) {
//     // 在发送请求之前可以重新处理config
//     // config.method = "get";
//     // console.log({'interceptorsRequest': config});
//     return config;
// }, function (error) {
//     // 对请求错误做些什么
//     return Promise.reject(error);
// });

// // 添加响应拦截器
// zhr.interceptors.response.use(function (response) {
//     // 对响应数据做点什么
//     console.log({'interceptorsResponse': response});
//     return response;
// }, function (error) {
//     // 对响应错误做点什么
//     return Promise.reject(error);
// });





  var
  // Map over zhr in case of overwrite
  _zhr = window.zhr,
  // Map over the zsl in case of overwrite
  _zsl = window.zsl;
  // 解除zsl和zhr变量的使用
  zhr.noConflict = function( deep ) {
    if ( window.zsl === zhr ) {
      window.zsl = _zsl;
    }
    if ( deep && window.zhr === zhr ) {
      window.zhr = _zhr;
    }
    return zhr;
  };
  if ( typeof noGlobal === "undefined" ) {
    window.zhr = window.zsl = zhr;
  }
  return zhr;
});

拦截器使用

// 使用案例

// // 添加请求拦截器
// zhr.interceptors.request.use(function (config) {
//     // 在发送请求之前可以重新处理config
//     // config.method = "get";
//     // console.log({'interceptorsRequest': config});
//     return config;
// }, function (error) {
//     // 对请求错误做些什么
//     return Promise.reject(error);
// });

// // 添加响应拦截器
// zhr.interceptors.response.use(function (response) {
//     // 对响应数据做点什么
//     console.log({'interceptorsResponse': response});
//     return response;
// }, function (error) {
//     // 对响应错误做点什么
//     return Promise.reject(error);
// });

发送请求案例

zhr({
    url: 'http://127.0.0.1',
    timeout: 10000,
    query: [
		{key: 'k1', value: 'v1'},
		['k2', 'v2'],
		'k3=v3'
	], // url => http://127.0.0.1?k1=v1&k2=v2&k3=v3
    body: {
	},
    method: 'post'
}).then(response => {
    console.log(response.$result)
}).catch(err => console.log(err))

参考 Sunny-lucking/howToBuildMyAxios手写axios

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值