Unfucking the ExternalInterface (2 of 2)
May 11th, 2008
⇠ Unfucking the ExternalInterface (1 of 2)
Step 2. Validate and Dispatch.
As much as I hate to admit it, JS tends to run quite a bit faster than AS3; So when the two are communicating, the real lag happens on the Flash side. It’s not such a problem if you just need to twiddle a boolean flag or set a variable, but generally when JS fires off some data, thats your cue to run a pile of code on it.
Let's take a look at that last sentance:
…when JS fires…data…your cue…pile of code
Sounds a lot like the event model, no? Well Adobe™ didn't think so, and the ExternalInterface’s sphincter-tight coupling and lack of validation are a slap in the face to anyone who disagrees.
Before we get ahead of ourselves, it’s worth bringing up my second gripe: Data validation.
Anyone who’s written AS3 for more than five minutes has seen a swf lock up as the result of a function receiving too much, too little, or unexpected data. Luckily the compiler catches these bugs and won’t allow you to publish until they’re resolved. When that safety-net is removed, most methods dealing with external data will wrap the incoming content inside a wildcard object like LoaderInfo or URLLoader. However, the ExternalInterface breaks from the rest of the API again and removes that last line of defense. By adding an EI callback, you expose that function to anything JS can send without filter.
Rest and relay.
Since there’s nothing to safeguard our inbound data, we need a small demilitarized zone where we can figure out what parameters we’re dealing with and cast them to a data type that Flash won’t choke on. It’s an ideal job for the rest statement:
ExternalInterface.addCallback('pieceOfAS', incomingJS) private function incomingJS(...args):Boolean{ // lets say the real function requires an int and a string if(args.length < 2 || args.length > 2){ return false; } try{ // cast and validate the integer var i:int = int(args[0]); if(i != args[0]){ trace('float received, expecting int'); return false; } // cast the string var s:String = String(args[1]); } catch (e:Error) { trace(e); return false; } trace('no errors encountered'); return true; }
This is mainly a big system of rejecting any data that isn’t perfect. We start by counting the arguments passed (if there aren’t 2, we eject), then we hard-cast the values to the types we’re expecting to get and if all goes well, only then do we alert JS that we got the message and we’re going to start work on it. Unfortunately, this method of validation is pretty specific to whatever function you’re calling, but it’s still a lot better than getting an incorrect value and having your swf flip out without letting you know.
So we’ve got the right data and we’re ready to process it. The last hurdle is solved with a custom event to wrap that data and throw it outside of the currently-running function to be processed at the start of the next frame. Let’s throw that line in the bottom of the previous code:
// ... trace('no errors encountered'); dispatchEvent(new JSEvent('pieceOfAS', i, s)); return true; // ...
And of course outside the incomingJS function we’ll need to attach a listener for that event.
addEventListener('pieceOfAS', processJS); private function processJS(e:JSEvent):void{ // ... }
Done! To recap, JavaScript target’s our swf and calls pieceOfAS(123, "werd"), that translates directly into a call on incomingJS(), which makes sure we’ve gotten exactly two variables, one integer and one string. The incomingJS function then puts those two arguments into an event (here's a tutorial if you’ve never made custom events) and dispatches it, and then returns ‘true’ to JS – we now have both AS and JS running in tandem again. Lastly our eventListener catches the custom event and routes it into the processJS() function, who can now run all the heavy code it wants without bogging anything else down.

September 27th, 2009 at 11:05 am
Good article but I have just tested it out (although not with JS but with C#) and I think it will not work as AS3 is a single threaded. Dispatching the event and return will not complete till event is captured and processed (if anyone or as many is listening to it) so the executing pointer will not return as mentioned in the article. I got here as I am facing a problem where I need the method call to be returned from the flash file as soon as possible and the flash filae can then go ahead and do any work it requires without using timers.