文章首发于个人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));