本篇文章分为三个部分:一、索引说明;二、使用环境介绍;三、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; |