ホームページ >ウェブフロントエンド >jsチュートリアル >jQuery が JSONP_jquery を使用するときに生成されるエラーを解決する

jQuery が JSONP_jquery を使用するときに生成されるエラーを解決する

WBOY
WBOYオリジナル
2016-05-16 15:28:061365ブラウズ

ドメイン

とは何ですか?クロスドメインとは、あるドメインのデータに別のドメインでアクセスすることを意味します。 データにアクセスせずに別のドメインのコンテンツをロードするだけの場合、iframe を使用するなど、クロスドメインは非常に簡単です。ただし、このデータを別のドメインからロードして使用する必要がある場合は、さらに面倒になります。セキュリティ上の理由から、ブラウザにはこの状況に対する厳しい制限があり、クロスドメイン要求を実装するにはクライアントとサーバーの両方でいくつかの設定を行う必要があります。

JSONP の概要

JSONP

(JSON with Padding) は一般的に使用されるクロスドメイン方式ですが、サポートされるのは JS スクリプトと JSON 形式のデータのみです。名前が示すように、
JSONP は、クロスドメイン リクエストを実装するためのシムとして JSON を使用する技術的手段です。基本原理は、HTML の 3f1c4e4b6b16bbbd69b2ee476dc4f83a タグが本質的にクロスドメインであるという事実を利用し、ロード後に別のドメインから JSON データをロードするために使用します。完了すると、コールバックが自動的に実行され、呼び出し元に通知されます。このプロセスには別のドメインからのサーバー サポートが必要なため、この方法でのクロスドメイン実装は任意ではありません。 JQuery による JSONP のサポート JQuery の Ajax オブジェクトは、crossDomain パラメーターを true に指定し、dataType

パラメーターを jsonp[1] に指定するか、省略形 getJSON() メソッド [2] を使用することにより、JSONP モードでのクロスドメイン リクエストをサポートします。例:


getJSON

を使用する場合、パラメータに jsoncallback=? を指定する必要があります。これは、JQuery が自動的に生成する値 (コールバック関数名) です。 ) を使用してパラメーター内の疑問符部分を置き換えて、
// 设置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=jQueryxxxxxxx

の形式でパラメーターを形成し、他のパラメーターとともに GET メソッドを使用してリクエストを作成します。 最初のメソッドを使用する場合、dataType パラメーターの値が jsonp として指定されている限り、JQuery はリクエスト アドレスの後に jsoncallback パラメーターを自動的に追加します。手動で追加する必要はありません。

JQuery クロスドメイン リクエストの欠陥: エラー処理 クロスドメインリクエストは失敗する可能性があります。たとえば、相手のサーバーのセキュリティ設定が当社からのリクエストの受け入れを拒否している(当社が相手の信頼リストに含まれていない)、ネットワークが利用できない、または相手のサーバーが使用できない場合があります。が閉じられているか、リクエストのアドレスまたはパラメータが正しくないため、サーバーはエラーなどを報告します。 JQuery では、ajax または getJSON を使用してリクエストが送信されると、jqXHR オブジェクト [3] が返されます。このオブジェクトは Promise プロトコルを実装しているため、done、fail、always、およびその他のインターフェイスを使用してコールバックを処理できます。たとえば、これを失敗コールバックで使用して、リクエストが失敗したときにエラーを処理できます。


这种方式能够处理“正常的错误”,例如超时、请求被中止、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是一个局部变量,从外部无法获取到。

那有没有解决办法呢?当然有:

  • 自己实现JSONP,不使用JQuery提供的
  • 修改JQuery源码(前提是你不是使用的CDN方式引用的JQuery)
  • 使用本文介绍的技巧

前两种不说了,如果愿意大可以选择。下面介绍另一种技巧。

通过以上源码可以发现,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 を使用するときに発生するエラーを学び、解決するのに役立つことを願っています。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。