July 16, 2003
JavaScript, asynchrony and closures
A couple of my GWS applications are driven by a fairly large chunk of JavaScript. There's some DHTML, then a slew of objects modeling Groove services, and they all call down to a SOAP helper, which uses XMLHTTP to talk to the Groove process. It's neat, and looks great, and I think it'll be maintenance-light. Rather, I thought it would be maintenance-light. But one itch just had to be scratched: responsiveness. If Groove is very busy, or is prompting for password at the time, the HTTP call blocks. Which in this case means that my non-Groove application stops responding, too. And the startup performance isn't great, because I kick in when the application loads, and my GWS calls pretty much block the user interface until they're done. To which there's only one solution: make the HTTP calls asynchronous. Here's where I'm really glad the stack is built in JavaScript: the result is nearly zero refactoring, because I can use closures. Gory details follow. The bottom of the stack looks like this (Some, but not much, pseudocode mixed in here to keep it readable):
So, I build a SOAP message and punt it down the wire; the return message is some XML, which gets passed to a parser and a factory where the result (an object or an enumeration) is built and returned to the caller. Errors throw. The caller has a nice high-level approach, for example:
And there's a middle layer, where the GrooveFileDescriptor, GrooveFilesToolData and my other classes live. Let's assume that we can make the HTTP calls asynchronously. My code will fire off a Web request, carry on running, and later be interrupted. But that SOAPCall() function can't have a meaningful return value anymore, because the request won't be done. Everything gets split into fragments. At the lowest level we have something like:
and at the top, the code seems likely to become What other parameters might my callback need? Plenty, as it turns out (local variables, not just "this"). And what about error handling - where will errors be thrown to? There's a simple switch on the HTTP object which makes it asynchronous: HTTP.open( sReq, sURL, true /* asynchronous */ );. Then you fire and forget; some while later, a COM event fires ("onreadystatechange"), and if readystate is "4", we have data. The "onreadystatechange" handler is can be set easily enough, but it doesn't get any context. Not even a pointer to the HTTP object which fired the event! How are we supposed to know what fired, to even find its readystate, let alone what we should do with it? A singleton caller? - too clunky. Global variables? - please no! Some sort of timer? Aaaargh! Closures (basically, anonymous inner functions, which retain the surrounding context) really help fix this. Additionally, I made the synchronous/asynchronous choice completely optional, by just adding an optional callback parameter to the generic SOAP call. Here's some of the reworked code (actually, this is nearly all of the rework right here). The only wrinkle I'm eliding below is error handling. Instead of throwing, I construct an error object, and pass it back; below we can see the caller check for one of these.
And the caller...
The callback is declared inline -- literally, as the second parameter to the .ReadFile call -- and it's a closure. --- For comparison...
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
vcard
archives: January 2005 December 2004 November 2004 October 2004 September 2004 August 2004 July 2004 June 2004 May 2004 April 2004 March 2004 February 2004 January 2004 December 2003 November 2003 October 2003 September 2003 August 2003 July 2003 June 2003 May 2003 April 2003 March 2003 February 2003 January 2003 December 2002 November 2002 October 2002 September 2002 August 2002 July 2002 June 2002 May 2002 April 2002 March 2002 February 2002 January 2002 December 2001 November 2001 October 2001 September 2001 August 2001 July 2001 June 2001 see also: {groove: [ ray, matt, paresh, mike, jeff, john ], other: [ /* more blogroll to follow */ ] } The views expressed on this weblog are mine alone and do not necessarily reflect the views of my employer. RSS 2.0 RSS 1.0 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||