手写ajax jsonp 以及对fetch的封装

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

foreword

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

一. 使用XMLHttpRequest 实现ajax请求

  • 注明:暂时不考虑IE5和IE6 ,所以没有做小的兼容性处理
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));
  • sync 注释
    默认值为true,即为异步请求,若async=false,则为同步请求
    当xhr为同步请求时,有如下限制:
    1. xhr必须为 0
    2. xhr.withCredentials必须为false
    3. xhr.responseType 必须为"" (注意置为"text"也不允许)
    4. 如果上面任何一个条件不满足,都会报错,所以我们一般要避免sync请求.

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

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

  • code
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));
注释解析
  • @1 请求模式 mode
    常用的 mode 属性值:
    1. same-origin: 表示只请求同域.如果你在该 mode 下进行的是跨域的请求的话, 那么就会报错.
    2. no-cors: 正常的网络请求, 主要应对于没有后台没有设置 Access-Control-Allow-Origin.话句话说, 就是用来处理 script, image 等的请求的.他是 mode 的默认值.
    3. cors: 用来发送跨域的请求.在发送请求时, 需要带上.
    4. cors-with-forced-preflight: 这是专门针对 xhr2 支持出来的 preflight(多发一个options请求), 会事先多发一次请求给 server, 检查该次请求的合法性。

  • @2 credentials(凭证)

    1. omit: 默认值, 忽略cookie的发送
    2. same-origin: 表示cookie只能同域发送, 不能跨域发送
    3. include: cookie既可以同域发送, 也可以跨域发送
  • @3 redirect
    可用的 redirect 模式: follow(自动重定向), error(如果产生重定向将自动终止并且抛出一个错误), 或者 manual(手动处理重定向).在Chrome中, Chrome 47 之前的默认值是 follow, 从 Chrome 47 开始是 manual。

  • @4 cache

    1. default :表示fetch请求之前将检查下http的缓存
    2. no-store: 表示fetch请求将完全忽略http缓存的存在, 这意味着请求之前将不再检查下http的缓存, 拿到响应后, 它也不会更新http缓存
    3. no-cache: 如果存在缓存, 那么fetch将发送一个条件查询request和一个正常的request, 拿到响应后, 它会更新http缓存
    4. reload:表示fetch请求之前将忽略http缓存的存在, 但是请求拿到响应后, 它将主动更新http缓存)
    5. force-cache: 表示fetch请求不顾一切的依赖缓存, 即使缓存过期了, 它依然从缓存中读取.除非没有任何缓存, 那么它将发送一个正常的request
    6. only-if-cached: 表示fetch请求不顾一切的依赖缓存, 即使缓存过期了, 它依然从缓存中读取.如果没有缓存, 它将抛出网络错误(该设置只在mode为”same-origin” 时有效).
注明:fetch这块原生就存在了很友好的API,这块可以参考MDN官方文档 fetch

三.jsonp封装

  • code
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代码传送门

你可能感兴趣的:(手写ajax jsonp 以及对fetch的封装)