// 设置crossDomain和dataType参数以使用JSONP$.ajax({ dataType: "jsonp", url: "http://www.example.com/xxx", crossDomain: true, data: { }}).done(function() { // 请求完成时的处理函数});// 使用getJSON$.getJSON("http://www.example.com/xxx?jsoncallback=?", { // 参数}, function() { // 请求完成时的处理函数});使用getJSON时,需要在参数中指定jsoncallback=?,这个就是前面所说的回调函数,JQuery会自动以一个随机生成的值(回调函数名)来替换该参数中的问号部分,从而形成jsoncallback=jQueryxxxxxxx这种形式的参数,然后和其他参数一起使用GET方式发出请求。var xhr = $.getJSON(...);xhr.fail(function(jqXHR, textStatus, ex) {alert("request failed, cause: " + ex.message);});这种方式能够处理“正常的错误”,例如超时、请求被中止、JSON解析出错等等。但它对那些“非正常的错误”,例如网络不通、服务器已关闭等情况的支持并不好。
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是一个局部变量,从外部无法获取到。var xhr = $.getJSON(...);// for "normal error" and ie 7, 8xhr.fail(function(jqXHR, textStatus, ex) {alert("request failed, cause: " + ex.message);});// for "abnormal error" in other browsersvar head = document.head || $("head")[0] || document.documentElement; // code from jqueryvar script = $(head).find("script")[0];script.onerror(function(evt) {alert("error");});这样我们就可以在所有浏览器(严格来说是绝大部分,因为我没有测试全部浏览器)里捕获到“非正常错误”了。// handle erroralert("error");// do some clean// delete script nodeif (script.parentNode) {script.parentNode.removeChild(script);}// delete jsonCallback global functionvar 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 failedxhr.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) * ex.name: "Error" */alert("failed");});// ie 8+, chrome and some other browsersvar head = document.head || $("head")[0] || document.documentElement; // code from jqueryvar script = $(head).find("script")[0];script.onerror = function(evt) {alert("error");// do some clean// delete script nodeif (script.parentNode) {script.parentNode.removeChild(script);}// delete jsonCallback global functionvar 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内核版本,其他浏览器暂时未测。