文章首發於個人github及我的博客,github請看https://github.com/huruji/blo...,轉載請註明出處。javascript
JSONP的原理就不細說了,就是利用script能夠跨域的特色來實現跨域,首先咱們考慮一個最簡單的jsonp,就是簡簡單單建立script標籤,
添加url的功能,以下:java
function jsonp(url) { const script = document.createElement('script'); script.src = url; script.type = 'text/javascript'; document.body.appendChild(script); }
此時咱們使用服務端的代碼以下:git
const http = require('http'); const data = {'data':'hello'}; const url = require('url'); const queryString = require('querystring'); http.createServer(function(req, res) { var params = url.parse(req.url); console.log(params); if(params.query && queryString.parse(params.query).callback) { console.log(1231232); const str = queryString.parse(params.query).callback + '(' + JSON.stringify(data) + ')'; return res.end(str) } res.end(JSON.stringify(data)); }).listen(5000);
這是咱們調用jsonp,假設咱們只是想要alert出返回的數據,以下:es6
function msg(res) { alert(res.data); } jsonp('http://localhost:5000?callback=msg');
這時候咱們運行代碼能夠發現已經正確彈出了相應的數據。
可是咱們會發現這裏的callback回調函數是一個全局的,這是不可取的,所以咱們須要進行一些修改,將處理修改成一個局部的,咱們能夠將其做爲一個回調函數來處理,以下:github
function jsonp(url, callback) { window.jsonpCallback = callback; const script = document.createElement('script'); script.src = url + '?callback=jsonpCallback'; script.type = 'text/javascript'; document.body.appendChild(script); } jsonp('http://localhost:5000', function(res) { alert(res.data); });
這時候咱們會發現咱們再也不須要在url中聲明相應的callback了,可是咱們仍是會發現一個問題,就是咱們將全部的callback都設置成了一個全局變量,這樣的緣由是由於咱們須要在數據請求完成以後調用這個方法,所以不得不設置爲一個全局變量。可是當咱們有多個請求,而且每一個請求的處理都是不同的時候,這個變量將會被覆蓋。這是不行的,所以咱們應該爲每一次請求設置一個惟一且不會衝突的變量,所以增長一個生成callback名字的方法以下:ajax
function generateJsonpCallback() { return `jsonpcallback_${Date.now()}_${Math.floor(Math.random() * 100000)}`; } function jsonp(url, callback) { const funcName = generateJsonpCallback(); window[funcName] = callback; const script = document.createElement('script'); script.src = `${url}?callback=${funcName}`; script.type = 'text/javascript'; document.body.appendChild(script); } jsonp('http://localhost:5000', function(res) { alert(res.data); }); jsonp('http://localhost:5000', function(res) { const text = document.createTextNode(res.data); document.body.appendChild(text); });
這時候咱們會發現咱們已經利用了一個相似於隨機ID的形式完成了屢次請求。
可是仍是有一個問題大量的請求以後,window中會含有大量的全局變量,並且還有大量的script標籤,這顯然不是咱們想要的,因此咱們能夠在請求完成以後刪除變量和script標籤。json
function generateJsonpCallback() { return `jsonpcallback_${Date.now()}_${Math.floor(Math.random() * 100000)}`; } function removeScript(id) { document.body.removeChild(document.getElementById(id)); } function removeFunc(name) { delete window[name]; } function jsonp(url, timeout = 3000, callback) { const funcName = generateJsonpCallback(); window[funcName] = callback; const script = document.createElement('script'); script.src = `${url}?callback=${funcName}`; script.id = funcName; script.type = 'text/javascript'; document.body.appendChild(script); setTimeout(() => { removeScript(funcName); removeFunc(funcName); }, timeout) } jsonp('http://localhost:5000', 3000, function(res) { alert(res.data); }); jsonp('http://localhost:5000', 3000, function(res) { const text = document.createTextNode(res.data); document.body.appendChild(text); });
咱們經過將利用一個timeout時間定時爲咱們清除相應的script標籤和全局變量就能夠了,這個定時時間的做用相似於ajax的timeout時間。
咱們全部的內容都是使用es6的,那爲何不使用Promise來處理呢,還要使用煩人的回調,接下來那就來Promise化吧。跨域
function jsonp(url, options = {timeout:3000}) { const timeout = options.timeout; return new Promise((resolve, reject) => { const funcName = generateJsonpCallback(); window[funcName] = (res) => { resolve(res); setTimeout(() => { removeScript(funcName); removeFunc(funcName); }, timeout) }; const script = document.createElement('script'); script.src = `${url}?callback=${funcName}`; script.id = funcName; script.type = 'text/javascript'; document.body.appendChild(script); }) }
調用只須要以下就能夠了app
jsonp('http://localhost:5000').then((res) => alert(res.data)); jsonp('http://localhost:5000').then((res) => { const text = document.createTextNode(res.data); document.body.appendChild(text); });
到目前爲止,一個較爲完整的jsonp就實現了,可是咱們仍是會以爲少了一些什麼,相信你已經看出來了,那就是錯誤處理。
迄今爲止,並無測試過若是這個script標籤加載不成功如何處理,判斷資源加載失敗,顯然使用的是onerror事件,咱們這就把他加上:dom
function jsonp(url, options = {timeout:3000}) { const timeout = options.timeout; let timeId; return new Promise((resolve, reject) => { const funcName = generateJsonpCallback(); window[funcName] = (res) => { resolve(res); timeId = setTimeout(() => { removeScript(funcName); removeFunc(funcName); }, timeout) }; const script = document.createElement('script'); script.src = `${url}?callback=${funcName}`; script.id = funcName; script.type = 'text/javascript'; document.body.appendChild(script); script.onerror = () => { reject(new Error(`fetch ${url} failed`)); removeScript(funcName); removeFunc(funcName); if(timeId) clearTimeout(timeId); } }) }
咱們能夠測試一下,輸入一個不存在的url:
jsonp('http://localhost:7000').then((res) => alert(res.data));
能夠發現這時正常處理錯誤了,能夠在控制檯看到相應的url獲取失敗,至此,完工;
至此全部的代碼簡單封裝以下:
(function(global){ function generateJsonpCallback() { return `jsonpcallback_${Date.now()}_${Math.floor(Math.random() * 100000)}`; } function removeScript(id) { document.body.removeChild(document.getElementById(id)); } function removeFunc(name) { delete global[name]; } function jsonp(url, options = {timeout:3000}) { const timeout = options.timeout; let timeId; return new Promise((resolve, reject) => { const funcName = generateJsonpCallback(); global[funcName] = (res) => { resolve(res); timeId = setTimeout(() => { removeScript(funcName); removeFunc(funcName); }, timeout) }; const script = document.createElement('script'); script.src = `${url}?callback=${funcName}`; script.id = funcName; script.type = 'text/javascript'; document.body.appendChild(script); script.onerror = () => { reject(new Error(`fetch ${url} failed`)); removeScript(funcName); removeFunc(funcName); if(timeId) clearTimeout(timeId); } }) } window.jsonp = jsonp; })(window);
測試代碼以下:
jsonp('http://localhost:5000').then((res) => alert(res.data)); jsonp('http://localhost:5000').then((res) => { const text = document.createTextNode(res.data); document.body.appendChild(text); }); jsonp('http://localhost:7000').then((res) => alert(res.data));