Статьи

XMLHttpRequest и закрытия Javascript

Следуя превосходным советам Саймона о замыканиях и выполнении JavaScript при загрузке страницы, получил электронное письмо от Скотта Рэймонда о том, как это можно применить к XMLHttpRequest, так что я решил поделиться некоторыми своими впечатлениями от ScriptServer .

Реализация XMLHttpRequest в Mozilla предоставляет свойства «onload» и «onerror», которым вы можете назначить свои собственные обратные вызовы, при этом обратный вызов автоматически передается объекту Event, через который вы можете вернуться к своему вызывающему объекту — лучше всего это увидеть, посмотрев на Mozblog nsXmlRpcClient.js — посмотрите, что они делают со своей функцией _onload.

К сожалению, реализация Microsoft ActiveX не требует, чтобы вы использовали свойство onreadystatechange, и, к счастью, Mozilla также поддерживает это (примечание — является ли XMLHttpRequest Mozilla первым, когда кто-то потянул любимый Microsoft «Embrace and Extend» на самих Microsoft?). Проблема в том, что ничего не получает автоматически, передается в функцию обратного вызова, назначенную onreadystatechange, но здесь закрытие оказывается полезным.

Остальное я оставлю на длинный кусок кода

 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <script type="text/javascript"> <!-- // Mozilla only implementation! // Constructor for generic HTTP client function HTTPClient() {}; // Add methods and properties as array HTTPClient.prototype = { url: null, // Instance of XMLHttpRequest xmlhttp: null, // Used to make sure multiple calls are not placed // with the same client object while another in progress callinprogress: false, // The user defined handler - see MyHandler below userhandler: null, init: function(url) { this.url = url; this.xmlhttp = new XMLHttpRequest(); }, // handler argument is a user defined object to be called asyncGET: function (handler) { // Prevent multiple calls if (this.callinprogress) { throw "Call in progress"; }; this.userhandler = handler; // Open an async request - third argument makes it async this.xmlhttp.open('GET',this.url,true); // Have to assign "this" to a variable - not sure why can't use directly var self = this; // Assign a closure to the onreadystatechange callback this.xmlhttp.onreadystatechange = function() { self.stateChangeCallback(self); } // Send the request this.xmlhttp.send(null); }, stateChangeCallback: function(client) { switch (client.xmlhttp.readyState) { // Request not yet made case 1: try { client.userhandler.onInit(); } catch (e) { /* Handler method not defined */ } break; // Contact established with server but nothing downloaded yet case 2: try { // Check for HTTP status 200 if ( client.xmlhttp.status != 200 ) { client.userhandler.onError( client.xmlhttp.status, client.xmlhttp.statusText ); // Abort the request client.xmlhttp.abort(); // Call no longer in progress client.callinprogress = false; } } catch (e) { /* Handler method not defined */ } break; // Called multiple while downloading in progress case 3: // Notify user handler of download progress try { // Get the total content length // -useful to work out how much has been downloaded try { var contentLength = client.xmlhttp.getResponseHeader("Content-Length"); } catch (e) { var contentLength = NaN; } // Call the progress handler with what we've got client.userhandler.onProgress( client.xmlhttp.responseText, contentLength ); } catch (e) { /* Handler method not defined */ } break; // Download complete case 4: try { client.userhandler.onLoad(client.xmlhttp.responseText); } catch (e) { /* Handler method not defined */ } finally { // Call no longer in progress client.xmlhttp.callinprogress = false; } break; } } } // A user defined handler to response to the XMLHTTPRequest var MyHandler = { onInit: function() { echo("About to send request<br>"); }, onError: function(status,statusText) { echo("Error: "+status+": "+statusText+"<br>"); }, onProgress: function(responseText,length) { echo("Downloaded "+responseText.length+" of "+length+"<br>"); }, onLoad: function(result) { echo("<pre>"+result+"</pre>"); }, } // Just a function to help display results function echo(string) { document.getElementById("results").innerHTML += string; } // Invoke the client function getPage() { // Modify this to some page var url = "http://localhost/test/test.txt"; var client = new HTTPClient(); client.init(url); try { client.asyncGET(MyHandler); } catch (e) { alert(e); } echo("Async request so still able to do stuff here<br>"); } </script> </head> <body> <a href="javascript:getPage();">getPage</a> <div id="results"> </div> </body> </html> Note I'm also using an asychronous request here - have heard people complain XMLHttpRequest is too slow, locking up the browser - an asychronous request helps you prevent that. Note also this is also Mozilla specific right now. Since versions 3.x of the ActiveX XMLHttpRequest it seems, for some reason I don't understand, they've stopped populating properties like "status" until we hit readyState 4 - that means you can't respond to errors or get information about the request until it's completely finished. For a cross browser implementation of something similar, see here (have yet to implement the closure though, hence using lots of globals) - example use here . 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <script type="text/javascript"> <!-- // Mozilla only implementation! // Constructor for generic HTTP client function HTTPClient() {}; // Add methods and properties as array HTTPClient.prototype = { url: null, // Instance of XMLHttpRequest xmlhttp: null, // Used to make sure multiple calls are not placed // with the same client object while another in progress callinprogress: false, // The user defined handler - see MyHandler below userhandler: null, init: function(url) { this.url = url; this.xmlhttp = new XMLHttpRequest(); }, // handler argument is a user defined object to be called asyncGET: function (handler) { // Prevent multiple calls if (this.callinprogress) { throw "Call in progress"; }; this.userhandler = handler; // Open an async request - third argument makes it async this.xmlhttp.open('GET',this.url,true); // Have to assign "this" to a variable - not sure why can't use directly var self = this; // Assign a closure to the onreadystatechange callback this.xmlhttp.onreadystatechange = function() { self.stateChangeCallback(self); } // Send the request this.xmlhttp.send(null); }, stateChangeCallback: function(client) { switch (client.xmlhttp.readyState) { // Request not yet made case 1: try { client.userhandler.onInit(); } catch (e) { /* Handler method not defined */ } break; // Contact established with server but nothing downloaded yet case 2: try { // Check for HTTP status 200 if ( client.xmlhttp.status != 200 ) { client.userhandler.onError( client.xmlhttp.status, client.xmlhttp.statusText ); // Abort the request client.xmlhttp.abort(); // Call no longer in progress client.callinprogress = false; } } catch (e) { /* Handler method not defined */ } break; // Called multiple while downloading in progress case 3: // Notify user handler of download progress try { // Get the total content length // -useful to work out how much has been downloaded try { var contentLength = client.xmlhttp.getResponseHeader("Content-Length"); } catch (e) { var contentLength = NaN; } // Call the progress handler with what we've got client.userhandler.onProgress( client.xmlhttp.responseText, contentLength ); } catch (e) { /* Handler method not defined */ } break; // Download complete case 4: try { client.userhandler.onLoad(client.xmlhttp.responseText); } catch (e) { /* Handler method not defined */ } finally { // Call no longer in progress client.xmlhttp.callinprogress = false; } break; } } } // A user defined handler to response to the XMLHTTPRequest var MyHandler = { onInit: function() { echo("About to send request<br>"); }, onError: function(status,statusText) { echo("Error: "+status+": "+statusText+"<br>"); }, onProgress: function(responseText,length) { echo("Downloaded "+responseText.length+" of "+length+"<br>"); }, onLoad: function(result) { echo("<pre>"+result+"</pre>"); }, } // Just a function to help display results function echo(string) { document.getElementById("results").innerHTML += string; } // Invoke the client function getPage() { // Modify this to some page var url = "http://localhost/test/test.txt"; var client = new HTTPClient(); client.init(url); try { client.asyncGET(MyHandler); } catch (e) { alert(e); } echo("Async request so still able to do stuff here<br>"); } </script> </head> <body> <a href="javascript:getPage();">getPage</a> <div id="results"> </div> </body> </html> Note I'm also using an asychronous request here - have heard people complain XMLHttpRequest is too slow, locking up the browser - an asychronous request helps you prevent that. Note also this is also Mozilla specific right now. Since versions 3.x of the ActiveX XMLHttpRequest it seems, for some reason I don't understand, they've stopped populating properties like "status" until we hit readyState 4 - that means you can't respond to errors or get information about the request until it's completely finished. For a cross browser implementation of something similar, see here (have yet to implement the closure though, hence using lots of globals) - example use here .

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <script type="text/javascript"> <!-- // Mozilla only implementation! // Constructor for generic HTTP client function HTTPClient() {}; // Add methods and properties as array HTTPClient.prototype = { url: null, // Instance of XMLHttpRequest xmlhttp: null, // Used to make sure multiple calls are not placed // with the same client object while another in progress callinprogress: false, // The user defined handler - see MyHandler below userhandler: null, init: function(url) { this.url = url; this.xmlhttp = new XMLHttpRequest(); }, // handler argument is a user defined object to be called asyncGET: function (handler) { // Prevent multiple calls if (this.callinprogress) { throw "Call in progress"; }; this.userhandler = handler; // Open an async request - third argument makes it async this.xmlhttp.open('GET',this.url,true); // Have to assign "this" to a variable - not sure why can't use directly var self = this; // Assign a closure to the onreadystatechange callback this.xmlhttp.onreadystatechange = function() { self.stateChangeCallback(self); } // Send the request this.xmlhttp.send(null); }, stateChangeCallback: function(client) { switch (client.xmlhttp.readyState) { // Request not yet made case 1: try { client.userhandler.onInit(); } catch (e) { /* Handler method not defined */ } break; // Contact established with server but nothing downloaded yet case 2: try { // Check for HTTP status 200 if ( client.xmlhttp.status != 200 ) { client.userhandler.onError( client.xmlhttp.status, client.xmlhttp.statusText ); // Abort the request client.xmlhttp.abort(); // Call no longer in progress client.callinprogress = false; } } catch (e) { /* Handler method not defined */ } break; // Called multiple while downloading in progress case 3: // Notify user handler of download progress try { // Get the total content length // -useful to work out how much has been downloaded try { var contentLength = client.xmlhttp.getResponseHeader("Content-Length"); } catch (e) { var contentLength = NaN; } // Call the progress handler with what we've got client.userhandler.onProgress( client.xmlhttp.responseText, contentLength ); } catch (e) { /* Handler method not defined */ } break; // Download complete case 4: try { client.userhandler.onLoad(client.xmlhttp.responseText); } catch (e) { /* Handler method not defined */ } finally { // Call no longer in progress client.xmlhttp.callinprogress = false; } break; } } } // A user defined handler to response to the XMLHTTPRequest var MyHandler = { onInit: function() { echo("About to send request<br>"); }, onError: function(status,statusText) { echo("Error: "+status+": "+statusText+"<br>"); }, onProgress: function(responseText,length) { echo("Downloaded "+responseText.length+" of "+length+"<br>"); }, onLoad: function(result) { echo("<pre>"+result+"</pre>"); }, } // Just a function to help display results function echo(string) { document.getElementById("results").innerHTML += string; } // Invoke the client function getPage() { // Modify this to some page var url = "http://localhost/test/test.txt"; var client = new HTTPClient(); client.init(url); try { client.asyncGET(MyHandler); } catch (e) { alert(e); } echo("Async request so still able to do stuff here<br>"); } </script> </head> <body> <a href="javascript:getPage();">getPage</a> <div id="results"> </div> </body> </html> Note I'm also using an asychronous request here - have heard people complain XMLHttpRequest is too slow, locking up the browser - an asychronous request helps you prevent that. Note also this is also Mozilla specific right now. Since versions 3.x of the ActiveX XMLHttpRequest it seems, for some reason I don't understand, they've stopped populating properties like "status" until we hit readyState 4 - that means you can't respond to errors or get information about the request until it's completely finished. For a cross browser implementation of something similar, see here (have yet to implement the closure though, hence using lots of globals) - example use here .