ES6跨域数据访问fetch-jsonp

潘佐
2023-12-01

本篇文章分为三个部分:一、索引说明;二、使用环境介绍;三、fetch-jsonp实现分析。


一、索引说明

本篇文章要说明的是一款跨域数据请求的库文件fetch-jsonp,这是一款github上开源文件,基于ES6下的Promise设计实现,可以在某些情况下替代$.ajax实现数据的跨域访问,github上有非常详细的API说明及使用实例,本文只列举一个简单的例子用于说明,具体还请移步GIT查看。

首先此方法只支持GET方法,而且方法基于ES6的Promise实现:

fetchJsonp('/访问的地址.jsonp')
  .then(function(response) {
    return response.json()
  }).then(function(json) {
    console.log('parsed json', json); // 在此处进行接收数据之后的操作
  }).catch(function(ex) {
    console.log('parsing failed', ex) // 此处是数据请求失败后的处理
  })

这样就可以实现跨域请求,还支持then方法异步处理,是不是特别简单。


二、使用环境介绍

这里说明下笔者的开发环境,也就是一步步想到使用fetch-jsonp而不是其他方法去实现跨域请求的原因,如果大家和我遇到了同样的状况,也可以尝试下使用此方法。

对于一个react开发的web网页项目,数据请求使用的方法不是$.ajax而是fetch(fetch是基于ES6浏览器提供的数据请求接口,可以替代$.ajax实现数据请求,基于Promise实现,写法便捷,使用方便,具体可查看在fetch中进行查看)。

开发过程不可避免的遇到了跨域请求,fetch方法是通过配置CORS 实现跨域(no-cors这个配置真心不能用,配置了之后只能够在浏览器控制台看到返回的数据,并不能返回到代码中处理),但是此配置在前端写好配置项的前提下,还需要后端配置下CORS才能实现。现实的开发环境是访问的后端数据接口是之前支持$.ajax的jsonp方式设置的,并且由于某些原因并不能去修改配置,因此网上海淘之后,发现了fetch-jsonp方法。

fetch-jsonp是github上的开源项目,使用简单:

安装 :npm install fetch-jsonp ;

引用 :import fetchJsonp from 'fetch-jsonp';

调用:

fetchJsonp('/users.jsonp', {
    jsonpCallback: 'custom_callback', // 回调函数名称,默认callback
  })
  .then(function(response) {
    return response.json()
  }).then(function(json) {
    console.log('parsed json', json)
  }).catch(function(ex) {
    console.log('parsing failed', ex)
  })

完美调用之前$.ajax的jsonp调用的数据接口,而且then方法实现增强了代码的易读性。

三、fetch-jsonp实现分析

使用fetch-jsonp解决了跨域问题,那下一步就是对fetch-jsonp进行分析了,毕竟使用公共库只能解决问题,知道公共库的实现方式才能提升自己。在fetch-jsonp中找到fetch-jsonp.js,代码之后100行左右,非常简洁,在此再次感谢fetch-jsonp项目的开发者,解决了笔者的数据跨域难题,具体的代码实现分析如下:

首先,跨域是通过在页面header中添加script标签实现数据请求:

    constjsonpScript=document.createElement('script');

    jsonpScript.setAttribute('src', `${url}${jsonpCallback}=${callbackFunction}`);

    if (options.charset) {

      jsonpScript.setAttribute('charset', options.charset);

    }

    jsonpScript.id= scriptId;

    document.getElementsByTagName('head')[0].appendChild(jsonpScript);

其实是对实现过程的具体说明

// 参数的初始化设置

constdefaultOptions= {

  timeout:5000, // 超时时间(毫秒)

  jsonpCallback:'callback', // 默认回调变量名称

  jsonpCallbackFunction:null,  // 回调函数名称

};

 

// 默认回调函数名称,若不设置则调用此方法,生成名称为‘jsonp_’+随机数

functiongenerateCallbackFunction() {

  return`jsonp_${Date.now()}_${Math.ceil(Math.random() *100000)}`;

}

 

// 删除申请的全局回调函数

functionclearFunction(functionName) {

  // IE8 throws an exception when you try to delete a property on window

  // http://stackoverflow.com/a/1824228/751089

  try {

    deletewindow[functionName];

  } catch (e) {

    window[functionName] =undefined;

  }

}

 

// 删除head中加载数据时生成的scripts

functionremoveScript(scriptId) {

  constscript=document.getElementById(scriptId);

  if (script) {

    document.getElementsByTagName('head')[0].removeChild(script);

  }

}

 

// 主函数

functionfetchJsonp(_url, options = {}) {  // 接收两个参数:_url(url地址)和options(参数设置,包含超时,回调变量及函数名称)

  // to avoid param reassign

  let url = _url;

  consttimeout= options.timeout || defaultOptions.timeout; // 设置超时时间,若未设置则取默认值

  constjsonpCallback= options.jsonpCallback || defaultOptions.jsonpCallback; // 设置回调变量名,若未设置则取默认值

 

  let timeoutId;

 

  returnnewPromise((resolve, reject) => { // 新建Promise,等待被resolve或reject方法触发

    constcallbackFunction= options.jsonpCallbackFunction ||generateCallbackFunction();

    constscriptId=`${jsonpCallback}_${callbackFunction}`;

 

    window[callbackFunction] = (response) => {  // 申请全局回调函数,数据返回成功后调用触发resolve,并返回请求返回的数据

      resolve({

        ok:true,

        // keep consistent with fetch API

        json: () =>Promise.resolve(response),

      });

 

      // 下面三项是为了重复调用fetch-jsonp时清除上次调用产生的定时、script及回调函数

      if (timeoutId) clearTimeout(timeoutId);  // 若存在定时事件则清除(超时事件为超时未返回数据时的错误提示,后面会有申请说明)

 

      removeScript(scriptId); // 移除head中加载数据产生的script

 

      clearFunction(callbackFunction); // 移除申请的全局回调函数

    };

 

    // Check if the user set their own params, and if not add a ? to start a list of params 拼接url地址

    url += (url.indexOf('?') ===-1) ?'?':'&';

 

   // 设置script标签的src、charset等,而后插入到页面的head中触发跨域数据加载

    constjsonpScript=document.createElement('script');

    jsonpScript.setAttribute('src', `${url}${jsonpCallback}=${callbackFunction}`);

    if (options.charset) {

      jsonpScript.setAttribute('charset', options.charset);

    }

    jsonpScript.id= scriptId;

    document.getElementsByTagName('head')[0].appendChild(jsonpScript);

 

   // 设置超时事件,若超时未返回数据,则触发reject提示请求失败,同时清除script及回调函数;

    timeoutId =setTimeout(() => {

      reject(newError(`JSONP request to ${_url} timed out`));

 

      clearFunction(callbackFunction);

      removeScript(scriptId);

      window[callbackFunction] = () => {

        clearFunction(callbackFunction);

      };

    }, timeout);

 

    // Caught if got 404/500 若加载script返回status为404/500,则同样触发reject提示请求失败,同事清除script及回调函数;

    jsonpScript.onerror= () => {

      reject(newError(`JSONP request to ${_url} failed`));

 

      clearFunction(callbackFunction);

      removeScript(scriptId);

      if (timeoutId) clearTimeout(timeoutId);

    };

  });

}

 

// export as global function

/*

let local;

if (typeof global !== 'undefined') {

  local = global;

} else if (typeof self !== 'undefined') {

  local = self;

} else {

  try {

    local = Function('return this')();

  } catch (e) {

    throw new Error('polyfill failed because global object is unavailable in this environment');

  }

}

local.fetchJsonp = fetchJsonp;

*/

 

exportdefault fetchJsonp;

 类似资料: