JQuery一个ajax的误解

同事碰到一个很奇怪的问题。问题描述如下: 有一个页面,在页面加载的时候会默认发送一次异步ajax请求。**如果在请求未完成的情况下**,刷新页面,就会执行一次ajax的回调,奇怪的是并没有获取到数据,用fiddler监测, 服务端是返回了数据的。(这里一定注意的是,请求未完成,如果模拟的话,可以在 XMLHttpRequest的open方法之后打断点。然后不执行send复现问题。至于为什么要在open之后, 后面会有解释)。 这个问题,让我们首先看JQuery是怎么封装的ajax(这里是1.4.1版本),源代码如下:

 1 ajax: function( origSettings ) {
 2     var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings);
 3     var jsonp, status, data,
 4             callbackContext = origSettings && origSettings.context || s,
 5             type = s.type.toUpperCase();
 6     // convert data if not already a string
 7     ....这里省略中间jsonp的实现
 8     var requestDone = false;
 9 
10     // Create the request object
11     var xhr = s.xhr();
12     
13     .....
14     // Wait for a response to come back
15     var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) {
16         // The request was aborted
17     if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" ) {
18             .....
19 
20     // The transfer is complete and the data is available, or the request timed out
21     } else if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") ) {
22         requestDone = true;
23         xhr.onreadystatechange = jQuery.noop;
24 
25         status = isTimeout === "timeout" ?
26             "timeout" :
27             !jQuery.httpSuccess( xhr ) ?
28                 "error" :
29                 s.ifModified && jQuery.httpNotModified( xhr, s.url ) ?
30                     "notmodified" :
31                     "success";
32 
33         var errMsg;
34 
35         if ( status === "success" ) {
36             // Watch for, and catch, XML document parse errors
37             try {
38                 // process the data (runs the xml through httpData regardless of callback)
39                 data = jQuery.httpData( xhr, s.dataType, s );
40             } catch(err) {
41                 status = "parsererror";
42                 errMsg = err;
43             }
44         }
45 
46         // Make sure that the request was successful or notmodified
47         if ( status === "success" || status === "notmodified" ) {
48             // JSONP handles its own success callback
49             if ( !jsonp ) {
50                 success();
51             }
52         } else {
53             jQuery.handleError(s, xhr, status, errMsg);
54         }
55 
56         // Fire the complete handlers
57         complete();
58 
59         if ( isTimeout === "timeout" ) {
60             xhr.abort();
61         }
62 
63         // Stop memory leaks
64         if ( s.async ) {
65             xhr = null;
66         }
67         }
68     };
69 
70     ....
71 
72     // Send the data
73     try {
74         xhr.send( type === "POST" || type === "PUT" || type === "DELETE" ? s.data : null );
75     } catch(e) {
76         jQuery.handleError(s, xhr, null, e);
77         // Fire the complete handlers
78         complete();
79     }
80 
81     
82 
83     function success() {
84         // If a local callback was specified, fire it and pass it the data
85         if ( s.success ) {
86             s.success.call( callbackContext, data, status, xhr );
87         }
88 
89         // Fire the global callback
90         if ( s.global ) {
91             trigger( "ajaxSuccess", [xhr, s] );
92         }
93     }
94 
95     .....
96 
97     // return XMLHttpRequest to allow aborting the request etc.
98     return xhr;
99   }

这里面最关键的,就是**onreadystatechange** 的实现。这个实现里面有2个分支,第一个分支是触发ajax完成的机制,没什么好说的,我们就直接看第二个分支。

if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") )

注意这里的判断条件xhr.readyState === 4也就是说当 XMLHttpRequest对象的readyState 等于4的时候,JQ就会认为整个ajax请求就结束了。然后调用success方法。我们看下success的实现:

 1   function success() {
 2         // If a local callback was specified, fire it and pass it the data
 3     if ( s.success ) {
 4         s.success.call( callbackContext, data, status, xhr );
 5     }
 6 
 7     // Fire the global callback
 8     if ( s.global ) {
 9         trigger( "ajaxSuccess", [xhr, s] );
10     }
11    }

代码很简单,就是调用了我们在$.ajax传入的配置对象里面定义的回调函数。那么,既然我们重新刷新页面的时候,调用了我们的回调函数,那么必然这里的代码执行了。那么这里的代码执行了也就代表在刷新页面的那一刻,**onreadystatechange**被触发了,并且ajax应该是完成了的。这个时候,就要看下JQ认为完成了ajax的条件是什么。JQ会维持一个自己的一个叫status的状态值。代码如下:

  status = isTimeout === "timeout" ?
    "timeout" :!jQuery.httpSuccess( xhr ) ?
        "error" :
        s.ifModified && jQuery.httpNotModified( xhr, s.url ) ?
        "notmodified" :
        "success";

我们看一下jQuery.httpSuccess这个方法的实现:

 1    httpSuccess: function( xhr ) {
 2     try {
 3         // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
 4         return !xhr.status && location.protocol === "file:" ||
 5             // Opera returns 0 when status is 304
 6             ( xhr.status >= 200 && xhr.status < 300 ) ||
 7             xhr.status === 304 || xhr.status === 1223 || xhr.status === 0;
 8     } catch(e) {}
 9 
10     return false;
11   }

关键就在xhr.status === 0这个状态值上。当然一开始我忽略了这里,导致绕了很多弯路,至于为什么关键就在这里,我们后面说。
既然问题是在这个判断上,那么我们就要复原刷新页面时,xhr.status到底是什么状态值呢?我写了测试的代码,代码如下:

 1  // Create the request object
 2   var xhr = s.xhr();
 3 
 4   var _l = window.localStorage.length;
 5   if (!xhr.id) xhr.id = _l;
 6   window.localStorage['xhr_id' + _l] = xhr.id;
 7   window.localStorage['xhr_readyState' + _l] = xhr.readyState;
 8   
 9   ....
10   
11   // Make sure that the request was successful or notmodified
12    if (status === "success" || status === "notmodified") {
13    window.localStorage['onchange_end_xhr_id' + _l] = xhr.id;
14    window.localStorage['onchange_end_status' + _l] = status;
15    window.localStorage['onchange_end_xhr_status' + _l] = xhr.status;
16    window.localStorage['onchange_end_xhr' + _l] = xhr.responseText;
17    window.localStorage['onchange_end_xhr_lastModified' + _l] = lastModified;
18    window.localStorage['onchange_end_xhr_etag' + _l] = etag;              

在JQ的代码里加入记录状态的逻辑,因为每次刷新浏览器页面的生命周期会结束然后重新开始,所以这里用仓储来记录状态值。最终记录结果如下:

{{:营销组:技术问题解决办法集锦:javascript:jq问题截图.png|}}

**state值为0!!!!**,也就是说服务端没有任何返回(所以我们模拟的时候,才只open,不send)。

之后我查阅了资料:

**1.readyState有四种状态值。原文如下:**

done DONE (numeric value 4) The data transfer has been completed or something went wrong during the transfer (e.g. infinite redirects).

也就是说,不止是服务器返回数据会赋值为4,而且是在获取数据过程错误的时候,也会置为4。

**2.wiki上看到了一句话:**

If the open method of the XMLHttpRequest object was invoked with the third parameter set to true for an asynchronous request, the onreadystatechange event listener will be automatically invoked for each of the following actions that change the readyState property of the XMLHttpRequest object.

也就是说,只要XMLHttpRequest 对象执行了open方法,那么就会自动的在readyState值变化的时候,调用onreadystatechange 事件。

**结论:**
当刷新页面的时候,如果这个时候异步ajax请求还没有完成,只是open了,那么浏览器认为这次请求结束,获取数据失败。所以这个时候readyState置为4(这时就调用了
onreadystatechange 事件),但是state却是0.而JQ在判断是否成功时,state===0也算是成功的(不知道为什么也认为成功的)!!!
所以仍然调用了我们的成功回调方法,却没有获到任何数据。


''参考资料:\\
https://en.wikipedia.org/wiki/XMLHttpRequest\\
https://xhr.spec.whatwg.org/#interface-xmlhttprequest\\
https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest\\
https://www.w3.org/TR/XMLHttpRequest1/

猜你喜欢

转载自www.cnblogs.com/wzgblogs/p/12145678.html