도메인이란 무엇인가요? 교차 도메인이란 한 도메인의 데이터에 다른 도메인의 데이터에 액세스하는 것을 의미합니다. 데이터에 접근하지 않고 다른 도메인의 콘텐츠만 로드한다면 iframe을 사용하는 등 크로스 도메인은 매우 간단합니다. 하지만 이 데이터를 다른 도메인에서 로드하여 사용해야 한다면 더욱 번거로운 일이 될 것입니다. 보안상의 이유로 브라우저는 이 상황에 대해 엄격한 제한을 두고 있으며, 도메인 간 요청을 구현하려면 클라이언트와 서버 모두에서 일부 설정을 지정해야 합니다.
JSONP 소개
JSONP(JSON with Padding)는 일반적으로 사용되는 크로스 도메인 방식이지만 JS 스크립트와 JSON 형식 데이터만 지원합니다. 이름에서 알 수 있듯이
JSONP는 JSON을 shim으로 사용하여 도메인 간 요청을 구현하는 기술적 수단입니다. 기본 원칙은 HTML의 3f1c4e4b6b16bbbd69b2ee476dc4f83a 태그가 본질적으로 크로스 도메인이라는 사실을 활용하고 이를 사용하여 로드가 완료된 후 다른 도메인에서 JSON 데이터를 로드하는 것입니다. 완료되면 콜백이 자동으로 실행됩니다. 이 함수는 호출자에게 알립니다. 이 프로세스에는 다른 도메인의 서버 지원이 필요하므로 이러한 방식의 도메인 간 구현은 임의적이지 않습니다.
JQuery의 JSONP 지원
JQuery의 Ajax 객체는 crossDomain 매개변수를 true로 지정하고 dataType
// 设置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() { // 请求完成时的处理函数 });을 사용할 때 매개변수에 jsoncallback=?을 지정해야 합니다. 이는 앞서 언급한 콜백 함수로 JQuery에서는 자동으로 생성된 값(콜백 함수 이름)을 사용합니다. ) 매개변수의 물음표 부분을 대체하여
jsoncallback=jQueryxxxxxxx 형식의 매개변수를 구성한 후, 다른 매개변수와 함께 GET 메소드를 사용하여 요청합니다. 첫 번째 방법을 사용할 때 dataType 매개변수의 값이 jsonp로 지정되어 있으면 JQuery는 자동으로 요청 주소 뒤에 jsoncallback 매개변수를 추가하므로 수동으로 추가할 필요가 없습니다.
JQuery 도메인 간 요청 결함: 오류 처리 예를 들어, 상대방 서버의 보안 설정이 우리의 요청 수락을 거부하거나(우리가 상대방의 신뢰 목록에 없음), 네트워크를 사용할 수 없거나, 상대방 서버가 실패할 수 있습니다. 폐쇄되었거나 요청 주소 또는 매개변수가 올바르지 않아 서버가 오류를 보고하는 등의 문제가 발생했습니다.
JQuery에서는 ajax 또는 getJSON을 사용하여 요청을 보내면 jqXHR 개체[3]가 반환됩니다. 이 객체는 Promise 프로토콜을 구현하므로 완료, 실패, 항상 및 기타 인터페이스를 사용하여 콜백을 처리할 수 있습니다. 예를 들어 요청이 실패할 때 오류를 처리하기 위해 실패 콜백에서 이를 사용할 수 있습니다.
这种方式能够处理“正常的错误”,例如超时、请求被中止、JSON解析出错等等。但它对那些“非正常的错误”,例如网络不通、服务器已关闭等情况的支持并不好。
例如当对方服务器无法正常访问时,在Chrome下你会在控制台看到一条错误信息:
JQuery不会处理该错误,而是选择“静静地失败”:fail回调不会执行,你的代码也不会得到任何反馈,所以你没有处理这种错误的机会,也无法向用户报告错误。
一个例外是在IE8。在IE8中,当网络无法访问时,3f1c4e4b6b16bbbd69b2ee476dc4f83a标签一样会返回加载成功的信息,所以JQuery无法根据3f1c4e4b6b16bbbd69b2ee476dc4f83a标签的状态来判断是否已成功加载,但它发现3f1c4e4b6b16bbbd69b2ee476dc4f83a标签“加载成功”后回调函数却没有执行,所以JQuery以此判断这是一个“解析错误”(回调代码没有执行,很可能是返回的数据不对导致没有执行或执行失败),因此返回的错误信息将是“xxxx was not called”,其中的xxxx为回调函数的名称。
也就是说,由于IE8(IE7也一样)的这种奇葩特性,导致在发生网络不通等“非正常错误”时,JQuery反而无法选择“静默失败”策略,于是我们可以由此受益,得到了处理错误的机会。例如在这种情况下,上面的例子将会弹出“xxxx was not called”的对话框。
解决方案
当遇到“非正常错误”时,除了IE7、8以外,JQuery的JSONP在较新的浏览器中全部会“静默失败”。但很多时候我们希望能够捕获和处理这种错误。
实际上在这些浏览器中,3f1c4e4b6b16bbbd69b2ee476dc4f83a标签在遇到这些错误时会触发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不会把这个3f1c4e4b6b16bbbd69b2ee476dc4f83a标签暴露给我们,所以我们没有机会为其添加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是一个局部变量,从外部无法获取到。
那有没有解决办法呢?当然有:
前两种不说了,如果愿意大可以选择。下面介绍另一种技巧。
通过以上源码可以发现,JQuery虽然没有暴露出script变量,但是它却“暴露”出了3f1c4e4b6b16bbbd69b2ee476dc4f83a标签的位置。通过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除了会静默失败,它还会留下一堆垃圾不去清理,即新创建的3f1c4e4b6b16bbbd69b2ee476dc4f83a标签和全局回调函数。虽然留在那也没什么大的危害,但如果能够顺手将其清理掉不是更好吗?所以我们可以这样实现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) * ex.name: '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를 사용할 때 발생하는 오류를 배우고 해결하는 데 도움이 되기를 바랍니다.