Adobe, MAKE SOME NOISE

or – Asynchronous Communication and the AS3 ExternalInterface

I just deleted four paragraphs of this post, since they could be summed up as “I hate ExternalInterface because it's slow,” which isn’t even an accurate grievance. ExternalInterface is pretty quick. It's the function’s being called by it that are slow.

My real frustration is that communication between Flash and JS is synchronous. That is, whenever Flash calls out to run a javascript function, all action inside Flash halts until the JS function is completed. This makes sense if you’re using getters and setters, but try anything bigger than that and you’ll start to see problems. At the very shallowest level, you’ll see your animation stutter every time you call out to a JS function that takes more than a couple milliseconds, at deeper levels you’ll enjoy long unexplained freezes and dropped calls.

Before the good stuff, a couple caveats: I’m not a JavaScript guy. I rely on two devs who work wonders with JS – whereas I curl up and stop functioning, the second I lose strict data-typing and custom classes. A lot of the credit for this method goes to them.

Second, the point of this workaround is to decouple AS and JS and let them run at the same time. Doing this sacrifices the return value you get if you wait for a function to complete before jumping back to the other side of the EI.

Step 1. Fix the outbound calls.
The good news first: JS is multithreaded. So if we can get our JS function to run in a different thread than the one targeted by Flash, we can get back to running ActionScript while JS worries about the real work. The secret (thanks to my JS guys!) is JavaScript's setTimeout() function. Here's how it works:

Normally to call a JS function you'd write something like this:

  1. const JS_FUNCTION:String = 'myJSFunction';
  2. const ARG:String = 'foo';
  3.  
  4. // myJSFunction('foo');
  5. ExternalInterface.call(JS_FUNCTION, ARG);

All we need to do is manually construct our JS function signature, and use that as setTimeout’s target:

  1. const JS_FUNCTION:String = 'myJSFunction';
  2. const ARG:String = 'foo';
  3. const JS_CALL:String = JS_FUNCTION + '("' + ARG + '")';
  4.  
  5. // setTimeout(myJSFunction('foo'), 0);
  6. ExternalInterface.call('setTimeout', JSCALL, 0);

What does this accomplish? In the first example Flash stops running until foo runs its course. In the second example, Flash is waiting on setTimeout to complete instead. JavaScript will automatically call foo in another thread and return control to Flash, which allows AS to start running again while the foo function can run slowly on the JS side without gumming things up. To see this in action try calling a large empty loop or an alert in JS while you've got some animation going inside Flash.

Next post: Inbound calls, custom events, and data validation, oh my!

continued: Unfucking the ExternalInterface (2 of 2) ⇢

8 Responses to “Unfucking the ExternalInterface (1 of 2)”

  1. socketBridge: Flash - Javascript Socket Bridge » Matt Haynes - Programming & Tech Blog Says:

    [...] I found a way around this by wrapping all the callbacks from Flash in a setTimeout method. This effectively opens a new javascript thread and as such straight away returns the original callback. I picked up this tip here. [...]

  2. Jonathan Spooner Says:

    Good job! Saved me a bunch of time.

  3. quirx Says:

    The Method works ok for functions with a predefined set of arguments. But if you somehow have a user-generated String as Argument, problems occurr if the string contains quotes or newline characters, etc.
    Javascript will throw an error like “unterminated string literal…”
    i wonder how we can fix this.

  4. Rob Says:

    Good point quirx – although that’s more of a limitation of the ExternalInterface in general. Try calling escape() on your strings in JS, and unescape() when you receive the param in AS.

  5. jeff karova Says:

    Has anyone had any issues with subsequent ExternalInterface calls? I’m using a time out method as mentioned here to make an ExternalInterface call, but it seems that any subsequent ExternalInterface call simply doesn’t occur. Oddly, it seems that every other ExternalInterface call is successful. Is there a way to ‘flush’ the ExternalInterface?

  6. Ronnie Swietek Says:

    I kept getting a foo is not defined error and changed the JS_CALL const to:

    const JS_CALL:String = JS_FUNCTION + ‘(“‘ + ARG + ‘”)’;

    and that fixed it

  7. Rob Says:

    Nice catch Ronnie, fixed it in the post.

  8. unterminated string Says:

    [...] mistake is assuming the behavior of the string replace method will impact all possible matches. …Calypso88 Blog Archive Unfucking the ExternalInterface (1 …… generated String as Argument, problems occurr if the string contains … const JS_CALL:String = [...]

Leave a Reply