前端蜗牛HTML5

手写ajax jsonp 以及对fetch的封装

2019-12-10  本文已影响0人  aichisuan

夫君子之行,静以修身,俭以养德。 ——诸葛亮

foreword

前端需要反复温故,以及敢于尝试新的技术。jQuery,Vue,React 用了好几年,我们习惯性使用ajax库进行一层简易的封装,然后向后台请求数据,而事实接触原生js请求并不是很多,所以手写了ajax jsonp fetch的封装,用来温故一下以前的知识.

一. 使用XMLHttpRequest 实现ajax请求

code
const isFunction = fn => typeof fn === 'function';

const ajaxHttp = (url, option = {}) => {
  if (!XMLHttpRequest) {
    throw Error('您的浏览器版本过低,请选择新版本的浏览器');
  }
  let {
    data = {},
    method = 'GET',
    // 是否异步 @async
    async = true,
    timeout = 0,
    header = {},
    ontimeout = () => {},
    onerror = () => {},
    onprogressFn
  } = option;

  method = method.toUpperCase();
  const isGet = method === 'GET';

  if (isGet) {
    // format str 拼接
    const str = Object.entries(data)
      .join('&')
      .split(',')
      .join('=');
    url = url.includes('?') ? `${url}&${str}` : `${url}${str ? '?' + str : ''}`;
  }

  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(method, url, async);
    xhr.timeout = timeout;
    // 设置请求头 open方法之后  send方法之前调用
    xhr.setRequestHeader('Content-Type', 'application/json');
    Object.entries(header).forEach(item => {
      xhr.setRequestHeader(item[0], item[1]);
    });

    // 错误回调
    isFunction(onerror) ? (xhr.onerror = onerror) : '';
    // 超时回调
    isFunction(ontimeout) ? (xhr.ontimeout = ontimeout) : '';
    // 判断onprogress是否支持 如果支持写入回调 这里一般用来获取上传进度的
    if ('onprogress' in xhr.upload && isFunction(onprogressFn)) {
      xhr.upload.onprogress = onprogressFn;
    }

    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          try {
            resolve(JSON.parse(this.responseText));
          } catch (error) {
            resolve(this.responseText);
          }
        } else {
          const resJson = {
            code: this.status,
            response: this.response
          };
          reject(resJson);
        }
      }
    };

    xhr.send(method === 'GET' ? null : JSON.stringify(data));
  });
};

const creaetAjax = type => {
  ajaxHttp[type] = (url, options) =>
    ajaxHttp(url, {
      ...options,
      method: type
    });
};

const methods = ['get', 'GET', 'POST', 'post', 'put', 'PUT', 'DELETE', 'delete'];
// 写入各个方法,可以使用ajaxHttp.get 等调用
methods.forEach(type => creaetAjax(type));

参考文档:你真的会使用XMLHttpRequest吗?

二.对原生fetch进行简易的封装

const errStatusReg = /[4,5]\d{2}/;

const fetchHttp = (url,options={})=>{
  let {
    method="GET",
    headers={},
    data={},
    mode="cors",
    credentials = "omit",
    redirect = "manual",
    cache = "default"
  } = options;
  method = method.toUpperCase();
  const isGet = method === 'GET';
  if(isGet){
    // format str 拼接
    const str = Object.entries(data).join('&').split(',').join('=');
    url = url.includes("?")?`${url}&${str}`:`${url}${str?'?'+ str:''}`;
  };
  return fetch(url,{
    method,
    // 请求的 body 信息:可能是一个 Blob、BufferSource、FormData、URLSearchParams 
    // 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。
    body:isGet?null:JSON.stringify(data),
    // http 请求头
    headers,
    // 请求模式 @注释1
    mode,
    // 表达的含义请求是否携带cookie  @注释2
    credentials,
    // 重定向 模式 @注释3
    redirect,
    // 请求的缓存模式 @注释4
    cache
  }).then(data=>{
    if (errStatusReg.test(data.status))throw Error(`${data.status}:${data.statusText}`);
    return data.json();
  })
};

const creaetFetch = (type)=>{
  fetchHttp[type] = (url,options)=>fetchHttp(url,{...options,method:type})
};

const methods = ["get","GET",'POST',"post","put","PUT","DELETE","delete"];

methods.forEach(type => creaetFetch(type));
注释解析
注明:fetch这块原生就存在了很友好的API,这块可以参考MDN官方文档 fetch

三.jsonp封装

const formatData = data => {
  // 这里要先处理特殊情况的字符  & = 等等
  return Object.entries(data)
    .map(item => [encodeURIComponent(item[0]), item[1]])
    .join('&')
    .split(',')
    .join('=');
};
/**
 *
 *
 * @param {*} url 传入的请求url
 * @param {*} options 包含两个参数 1. get请求的参数对象data,以及和后台协商好的回调函数名称
 * @returns
 */
const jsonpHttp = (url, options) => {
  options = options || {};
  options.data = options.data || {};

  const headDOM = document.querySelector('head');
  const scriptDOM = document.createElement('script');
  // 这里传入的是url所以存在一些特殊的操作
  const str = formatData(options.data);

  return new Promise((resolve, reject) => {
    const CB = jsonoResData => {
      window[options[`jsonpCB`]] = null;
      headDOM.removeChild(scriptDOM);
      clearTimeout(scriptDOM.timer);
      resolve(jsonoResData);
    };
    window[options[`jsonpCB`]] = CB;

    // 超时处理
    if (options.timeout) {
      scriptDOM.timer = setTimeout(() => {
        headDOM.removeChild(scriptDOM);
        window[options[`jsonpCB`]] = null;
        reject({
          timeout: true,
          message: '请求超时'
        });
      }, options.timeout * 1000);
    }
    const urlFormat = url.includes('?') ? `${url}&${str}` : `${url}${str ? '?' + str : ''}`;
    // 多加一个jsonp的回调函数名称
    scriptDOM.src = `${urlFormat}&jsonpCB=${options[`jsonpCB`]}`;
    headDOM.appendChild(scriptDOM);
  });
};

如有纰漏之处望能够给出指正,有帮助的话动动小手点个赞把。

github代码传送门

上一篇 下一篇

猜你喜欢

热点阅读