解决jQuery使用JSONP时产生的错误
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
解决jQuery使⽤JSONP时产⽣的错误
什么是域,简单来说就是协议+域名或地址+端⼝,3者只要有任何⼀个不同就表⽰不在同⼀个域。
跨域,就是在⼀个域中访问另⼀个域的数据。
如果只是加载另⼀个域的内容,⽽不需要访问其中的数据的话,跨域是很简单的,⽐如使⽤iframe。
但如果需要从另⼀个域加载并使⽤这些数据的话,就会⽐较⿇烦。
为了安全性,浏览器对这种情况有着严格的限制,需要在客户端和服务端同时做⼀些设置才能实现跨域请求。
JSONP简介
JSONP(JSON with Padding)是⼀种常⽤的跨域⼿段,但只⽀持JS脚本和JSON格式的数据。
顾名思义,JSONP是利⽤JSON作为垫⽚,从⽽实现跨域请求的⼀种技术⼿段。
其基本原理是利⽤HTML的<script>标签天⽣可以跨域这⼀特点,⽤其加载另⼀个域的JSON数据,加载完成后会⾃动运⾏⼀个回调函数通知调⽤者。
此过程需要另⼀个域的服务端⽀持,所以这种⽅式实现的跨域并不是任意的。
JQuery对JSONP的⽀持
JQuery的Ajax对象⽀持JSONP⽅式的跨域请求,⽅法是将crossDomain参数指定为true并且将dataType参数指定为jsonp[1],或者使⽤简写形式:getJSON()⽅法[2]。
例如:
// 设置crossDomain和dataType参数以使⽤JSONP
$.ajax({
dataType: "jsonp",
url: "/xxx",
crossDomain: true,
data: {
}
}).done(function() {
// 请求完成时的处理函数
});
// 使⽤getJSON
$.getJSON("/xxx?jsoncallback=?", {
// 参数
}, function() {
// 请求完成时的处理函数
});
使⽤getJSON时,需要在参数中指定jsoncallback=?,这个就是前⾯所说的回调函数,JQuery会⾃动以⼀个随机⽣成的值(回调函数名)来替换该参数中的问号部分,从⽽形成jsoncallback=jQueryxxxxxxx这种形式的参数,然后和其他参数⼀起使⽤GET⽅式发出请求。
使⽤第⼀种⽅式时,只要将dataType参数的值指定为jsonp,JQuery就会⾃动在请求地址后⾯加上jsoncallback参数,因此⽆需⼿动添加。
JQuery跨域请求的缺陷:错误处理
跨域请求可能会失败,⽐如对⽅服务器的安全设置拒绝接受来⾃我⽅的请求(我⽅不在对⽅的信任列表中),或者⽹络不通,或对⽅服务器已关闭,或者请求地址或参数不正确导致服务器报错等等。
在JQuery中,当使⽤ajax或getJSON发送请求后会返回⼀个jqXHR对象[3]。
该对象实现了Promise协议,所以我们可以使⽤它的done、fail、always等接⼝来处理回调。
例如我们可以⽤在它的fail回调中进⾏请求失败时的错误处理:
var xhr = $.getJSON(...);
xhr.fail(function(jqXHR, textStatus, ex) {
alert('request failed, cause: ' + ex.message);
});
这种⽅式能够处理“正常的错误”,例如超时、请求被中⽌、JSON解析出错等等。
但它对那些“⾮正常的错误”,例如⽹络不通、服务器已关闭等情况的⽀持并不好。
例如当对⽅服务器⽆法正常访问时,在Chrome下你会在控制台看到⼀条错误信息:
JQuery不会处理该错误,⽽是选择“静静地失败”:fail回调不会执⾏,你的代码也不会得到任何反馈,所以你没有处理这种错误的机会,也⽆法向⽤户报告错误。
⼀个例外是在IE8。
在IE8中,当⽹络⽆法访问时,<script>标签⼀样会返回加载成功的信息,所以JQuery⽆法根据<script>标
签的状态来判断是否已成功加载,但它发现<script>标签“加载成功”后回调函数却没有执⾏,所以JQuery以此判断这是⼀个“解析错误”(回调代码没有执⾏,很可能是返回的数据不对导致没有执⾏或执⾏失败),因此返回的错误信息将是“xxxx was not called”,其中的xxxx为回调函数的名称。
也就是说,由于IE8(IE7也⼀样)的这种奇葩特性,导致在发⽣⽹络不通等“⾮正常错误”时,JQuery反⽽⽆法选择“静默失败”策略,于是我们可以由此受益,得到了处理错误的机会。
例如在这种情况下,上⾯的例⼦将会弹出“xxxx was not called”的对话框。
解决⽅案
当遇到“⾮正常错误”时,除了IE7、8以外,JQuery的JSONP在较新的浏览器中全部会“静默失败”。
但很多时候我们希望能够捕获和处理这种错误。
实际上在这些浏览器中,<script>标签在遇到这些错误时会触发error事件。
例如如果是我们⾃⼰来实现JSONP的话可以这样:
var ele = document.createElement('script');
ele.type = "text/javascript";
ele.src = '...';
ele.onerror = function() {
alert('error');
};
ele.onload = function() {
alert('load');
};
document.body.appendChild(ele);
在新浏览器中,当发⽣错误时将会触发error事件,从⽽执⾏onerror回调弹出alert对话框:
但是⿇烦在于,JQuery不会把这个<script>标签暴露给我们,所以我们没有机会为其添加onerror事件处理器。
下⾯是JQuery实现JSONP的主要代码:
jQuery.ajaxTransport( "script", function(s) {
if ( s.crossDomain ) {
var script,
head = document.head || jQuery("head")[0] || document.documentElement;
return {
send: function( _, callback ) {
script = document.createElement("script");
script.async = true;
...
script.src = s.url;
script.onload = script.onreadystatechange = ...;
head.insertBefore( script, head.firstChild );
},
abort: function() {
...
}
};
}
});
可以看到script是⼀个局部变量,从外部⽆法获取到。
那有没有解决办法呢?当然有:
⾃⼰实现JSONP,不使⽤JQuery提供的
修改JQuery源码(前提是你不是使⽤的CDN⽅式引⽤的JQuery)
使⽤本⽂介绍的技巧
前两种不说了,如果愿意⼤可以选择。
下⾯介绍另⼀种技巧。
通过以上源码可以发现,JQuery虽然没有暴露出script变量,但是它却“暴露”出了<script>标签的位置。
通过send⽅法的最后⼀句:
head.insertBefore( script, head.firstChild );可以知道这个动态创建的新创建标签被添加为head的第⼀个元素。
⽽我们反其道⽽⾏之,只要能获得这个head元素,不就可以获得这个script了吗?head是什么呢?继续看源码,看head是怎么来的:
head = document.head || jQuery("head")[0] || document.documentElement;原来如此,我们也⽤同样的⽅法获取就可以了,所以补全前⾯的那个例⼦,如下:
var xhr = $.getJSON(...);
// for "normal error" and ie 7, 8
xhr.fail(function(jqXHR, textStatus, ex) {
alert('request failed, cause: ' + ex.message);
});
// for 'abnormal error' in other browsers
var head = document.head || $('head')[0] || document.documentElement; // code from jquery
var script = $(head).find('script')[0];
script.onerror(function(evt) {
alert('error');
});
这样我们就可以在所有浏览器(严格来说是绝⼤部分,因为我没有测试全部浏览器)⾥捕获到“⾮正常错误”了。
这样捕获错误还有⼀个好处:在IE7、8之外的其他浏览器中,当发⽣⽹络不通等问题时,JQuery除了会静默失败,它还会留下⼀堆垃圾不去清理,即新创建的<script>标签和全局回调函数。
虽然留在那也没什么⼤的危害,但如果能够顺⼿将其清理掉不是更好吗?所以我们可以这样实现onerror:
// handle error
alert('error');
// do some clean
// delete script node
if (script.parentNode) {
script.parentNode.removeChild(script);
}
// delete jsonCallback global function
var src = script.src || '';
var idx = src.indexOf('jsoncallback=');
if (idx != -1) {
var idx2 = src.indexOf('&');
if (idx2 == -1) {
idx2 = src.length;
}
var jsonCallback = src.substring(idx + 13, idx2);
delete window[jsonCallback];
}
这样⼀来就趋于完美了。
完整代码
function jsonp(url, data, callback) {
var xhr = $.getJSON(url + '?jsoncallback=?', data, callback);
// request failed
xhr.fail(function(jqXHR, textStatus, ex) {
/*
* in ie 8, if service is down (or network occurs an error), the arguments will be:
*
* testStatus: 'parsererror'
* ex.description: 'xxxx was not called' (xxxx is the name of jsoncallback function)
* ex.message: (same as ex.description)
* : 'Error'
*/
alert('failed');
});
// ie 8+, chrome and some other browsers
var head = document.head || $('head')[0] || document.documentElement; // code from jquery
var script = $(head).find('script')[0];
script.onerror = function(evt) {
alert('error');
// do some clean
// delete script node
if (script.parentNode) {
script.parentNode.removeChild(script);
}
// delete jsonCallback global function
var src = script.src || '';
var idx = src.indexOf('jsoncallback=');
if (idx != -1) {
var idx2 = src.indexOf('&');
if (idx2 == -1) {
idx2 = src.length;
}
var jsonCallback = src.substring(idx + 13, idx2);
delete window[jsonCallback];
}
};
}
以上代码在IE8、IE11、Chrome、FireFox、Opera、360下测试通过,其中360是IE内核版本,其他浏览器暂时未测。
希望本⽂对⼤家学习,帮助⼤家解决jQuery使⽤JSONP时产⽣的错误。