ES6跨域數據訪問fetch-jsonp

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章