50行不到實現Promise化的jsonp

飛躍瘋人院發表於2019-03-05

第一次發文章,有點小緊張。

什麼是同源策略?

為什麼要有跨域限制?

為什麼script標籤能跨域?

這些想必不用說明了,我們切入正題:

再學習黃老師的【Vue 2.0開發企業級移動端音樂Web APP】時,因為課程的資料是通過Jsonp獲取的,當時聽到講解原理,不甚理解。於是點開jsonp包的原始碼研磨一番,原來實現原理是這樣的:

50行不到實現Promise化的jsonp

1. 客戶端需要在自己這邊有一個定義好的接收服務端資料的函式

2. 使用script標籤發起一個服務端地址的請求

3. 服務端響應,對應的函式執行,傳參完成

這是最簡單的jsonp實現,這裡需要寫一個函式,需要寫一個script標籤,好像一點都不高階~

接下來我們手寫一個jsonp,當然了,服務端怎麼知道你本地的準備函式是什麼,需要客戶端告訴它,而且服務端會根據客戶端提供的其他引數動態生成資料給到我們。所以我們主要是實現以下幾個點:

1. 呼叫時動態建立script標籤

2. 註冊一個全域性的方法等待被執行

3. 等待函式一定要在標籤發起請求之前準備好

4. 得到資料之後移除建立的script標籤

5. 函式名不能重名,可能會同時發起多個請求

從上所述,在這裡我們的方法需要以下幾個引數:

. url: 後端地址

. prefix: 執行函式名的字首,字尾使用自增來確保唯一性

. param: 與後端協商好的發起jsonp請求時的欄位

. timeout: 請求超時時間

. data: 服務端需要的其他引數

jsonp實現跨域請求也有自己的侷限,如只能發起get請求,原有的jsonp包url後面的引數需要自己新增上,使用的是回撥函式的形式處理資料,使用的是ES5的寫法,有了以上幾個問題,我們可以簡單的重構下:

// 其他引數在opts內
function jsonp(url, opts) {
    // 實現Promise化
    return new Promise((resolve, reject) => {
      // 自增值初始化 
      let count = 0;
      //設定預設引數
      const { 
        prefix = '__jp',
        param = 'callback',
        timeout = 60000,
        data = {}
      } = opts;
      let name = prefix + count++;
      let timer;
      //清除script標籤以及註冊的全域性函式以及超時定時器
      function cleanup() { // 清除函式
        if (script.parentNode) {
          script.parentNode.removeChild(script);
          window[name] = null;
          if (timer) {
            clearTimeout(timer);
          }
        }
      }
      if (timeout) { // 超時
        timer = setTimeout(() => {
          cleanup();
          reject('timeout');
        }, timeout);
      }
      // 註冊全域性函式,等待執行中...
      window[name] = res => {
        // 只要這個函式一執行,就表示請求成功,可以使用清除函式了
        if (window[name]) {
          cleanup();
        }
        // 將請求到的資料扔給then
        resolve(res);
      }
      // 以下將data物件格式的引數拼接到url的後面
      let str = '';
      for (const key in data) {
        const value = data[key] !== undefined ? data[key] : '';
        str += `&${key}=${encodeURIComponent(value)}`;
      }
      url = url + (url.indexOf('?') > 0 ? '' : '?') + str.substr(1);
      最後加上與服務端協商的jsonp請求欄位
      url = `${url}&${param}=${name}`;
      const script = document.createElement('script');
      script.src = url;
      // 以下這條執行且成功後,全域性等待函式就會被執行
      document.head.appendChild(script);
    })
  }
複製程式碼

# 大功告成,我們請求一個QQ音樂的輪播圖資料試下:

const url = 'https://c.y.qq.com/musichall/fcgi-bin/fcg_yqqhomepagerecommend.fcg';
  const opts = {
    data: {
      g_tk: 1928093487,
      inCharset: 'utf-8',
      outCharset: 'utf-8',
      notice: 0,
      format: 'jsonp',
      platform: 'h5',
      uin: 0,
      needNewCode: 1
    },
    // QQ音樂介面Jsonp欄位
    param: 'jsonpCallback'
  }
  
  jsonp(url, opts)
    .then(res => {
      console.log(res);
    })
    .catch(ex => {
      console.log(ex);
    })
複製程式碼

資料請求成功!

50行不到實現Promise化的jsonp

相關文章