首先背景就是测试同学发现我们的网页在ie9中展示不正确,实际是所有异步的接口都没有执行。然后我就开始了苦逼的排查过程。我们所有异步接口都是使用jQuery的ajax方法发出的,使用的jquery版本是1.11.0。
我最先定位到的是ajax方法返回status=0,statusText=No Transport。然后开始了我的查找问题之旅,我在网上所查出的资料都说这个是由于跨域造成的,加上$.support.cros=true
就可以。但实际我们项目访问的接口并不属于跨域。所以虽然加上这个属性可以解决问题,但并没有找到根源。
于是我开始看jquery的源码,先定位了发生错误的代码段,先贴上报错点:
1 // Get transport 2 transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); 3 4 // If no transport, we auto-abort 5 if ( !transport ) { 6 done( -1, "No Transport" ); 7 } else { 8 jqXHR.readyState = 1; 9 10 // Send global event 11 if ( fireGlobals ) { 12 globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); 13 } 14 // Timeout 15 if ( s.async && s.timeout > 0 ) { 16 timeoutTimer = setTimeout(function() { 17 jqXHR.abort("timeout"); 18 }, s.timeout ); 19 } 20 21 try { 22 state = 1; 23 transport.send( requestHeaders, done ); 24 } catch ( e ) { 25 // Propagate exception as error if not done 26 if ( state < 2 ) { 27 done( -1, e ); 28 // Simply rethrow otherwise 29 } else { 30 throw e; 31 } 32 } 33 }
此处transport =false。刚开始并不能完全看懂jquery源码,于是请教了我们公司的一位大神,在大神的指导下我开始排查为何transport =false。经过万能的断点调试,发现是由于下列代码中的options.crossDomain = true
导致的不能进入此段正确发送请求的关键代码(具体的代码作用不作分析)。到此终于定位罪魁祸首是最初我为了在html页面可以直接访问api服务器而增加的crossDomain=true
的设置(因为html和api服务器属于不同域名)。(此处并未结束,请继续往下看)
1 // Determine support properties 2 support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); 3 xhrSupported = support.ajax = !!xhrSupported; 4 5 // Create transport if the browser can provide an xhr 6 if ( xhrSupported ) { 7 8 jQuery.ajaxTransport(function( options ) { 9 // Cross domain only allowed if supported through XMLHttpRequest 10 if ( !options.crossDomain || support.cors ) { 11 12 var callback; 13 14 return { 15 send: function( headers, complete ) { 16 var i, 17 xhr = options.xhr(), 18 id = ++xhrId; 19 20 // Open the socket 21 xhr.open( options.type, options.url, options.async, options.username, options.password ); 22 23 // Apply custom fields if provided 24 if ( options.xhrFields ) { 25 for ( i in options.xhrFields ) { 26 xhr[ i ] = options.xhrFields[ i ]; 27 } 28 } 29 30 // Override mime type if needed 31 if ( options.mimeType && xhr.overrideMimeType ) { 32 xhr.overrideMimeType( options.mimeType ); 33 } 34 35 // X-Requested-With header 36 // For cross-domain requests, seeing as conditions for a preflight are 37 // akin to a jigsaw puzzle, we simply never set it to be sure. 38 // (it can always be set on a per-request basis or even using ajaxSetup) 39 // For same-domain requests, won't change header if already provided. 40 if ( !options.crossDomain && !headers["X-Requested-With"] ) { 41 headers["X-Requested-With"] = "XMLHttpRequest"; 42 } 43 44 // Set headers 45 for ( i in headers ) { 46 // Support: IE<9 47 // IE's ActiveXObject throws a 'Type Mismatch' exception when setting 48 // request header to a null-value. 49 // 50 // To keep consistent with other XHR implementations, cast the value 51 // to string and ignore `undefined`. 52 if ( headers[ i ] !== undefined ) { 53 xhr.setRequestHeader( i, headers[ i ] + "" ); 54 } 55 } 56 57 // Do send the request 58 // This may raise an exception which is actually 59 // handled in jQuery.ajax (so no try/catch here) 60 xhr.send( ( options.hasContent && options.data ) || null ); 61 62 // Listener 63 callback = function( _, isAbort ) { 64 var status, statusText, responses; 65 66 // Was never called and is aborted or complete 67 if ( callback && ( isAbort || xhr.readyState === 4 ) ) { 68 // Clean up 69 delete xhrCallbacks[ id ]; 70 callback = undefined; 71 xhr.onreadystatechange = jQuery.noop; 72 73 // Abort manually if needed 74 if ( isAbort ) { 75 if ( xhr.readyState !== 4 ) { 76 xhr.abort(); 77 } 78 } else { 79 responses = {}; 80 status = xhr.status; 81 82 // Support: IE<10 83 // Accessing binary-data responseText throws an exception 84 // (#11426) 85 if ( typeof xhr.responseText === "string" ) { 86 responses.text = xhr.responseText; 87 } 88 89 // Firefox throws an exception when accessing 90 // statusText for faulty cross-domain requests 91 try { 92 statusText = xhr.statusText; 93 } catch( e ) { 94 // We normalize with Webkit giving an empty statusText 95 statusText = ""; 96 } 97 98 // Filter status for non standard behaviors 99 100 // If the request is local and we have data: assume a success 101 // (success with no data won't get notified, that's the best we 102 // can do given current implementations) 103 if ( !status && options.isLocal && !options.crossDomain ) { 104 status = responses.text ? 200 : 404; 105 // IE - #1450: sometimes returns 1223 when it should be 204 106 } else if ( status === 1223 ) { 107 status = 204; 108 } 109 } 110 } 111 112 // Call complete if needed 113 if ( responses ) { 114 complete( status, statusText, responses, xhr.getAllResponseHeaders() ); 115 } 116 }; 117 118 if ( !options.async ) { 119 // if we're in sync mode we fire the callback 120 callback(); 121 } else if ( xhr.readyState === 4 ) { 122 // (IE6 & IE7) if it's in cache and has been 123 // retrieved directly we need to fire the callback 124 setTimeout( callback ); 125 } else { 126 // Add to the list of active xhr callbacks 127 xhr.onreadystatechange = xhrCallbacks[ id ] = callback; 128 } 129 }, 130 131 abort: function() { 132 if ( callback ) { 133 callback( undefined, true ); 134 } 135 } 136 }; 137 } 138 }); 139 }
到此本来应该结束了,但并没有,虽然我增加了crossDomain=true
的设置,但为何其他浏览器可以正常访问,而ie9不行呢。我继续调试代码,最终发现其他浏览器上述代码中support.cors = true
,而ie9下这个属性等于false。从上述代码中也可以看到,这个属性的判断来自于support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported )
,其中xhrSupported= new window.XMLHttpRequest()
,ie9中XMLHttpRequest没有withCredentials属性。也就是说这个问题是由于我的乱用属性加上各浏览器兼容性问题而导致的。