美文网首页前端蜗牛HTML5
手写ajax jsonp 以及对fetch的封装

手写ajax jsonp 以及对fetch的封装

作者: 禾小沐的技术与生活 | 来源:发表于2019-12-10 15:44 被阅读0次

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

    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的封装

        本文链接:https://www.haomeiwen.com/subject/uzccgctx.html