本篇文章分爲三個部分:一、索引說明;二、使用環境介紹;三、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; |