Debugging ActionScript That Triggers No Errors

Version 2

    Code That Doesn't Work


    This is a common lament. Adobe forum posters frequently state they see no error message but their code doesn't work.  Finding the problem and then the solution always involves the same steps:  Use the trace() function.  And, then use it again and again and again until you pinpoint the problem.

    You can always pinpoint the problem using the trace() function.  For example, you click a button that's supposed to load a swf but when you click your button, your swf doesn't load.

    Well, that's just the way a typical forum post would read.  But everyone that's been answering forum posts for more than a few months knows you can't assume the poster is correct. 

    The only things you can safely assume are you click something and you fail to see what you expect to see (the loaded swf). 

    That might mean the swf didn't load.  But it also might mean you didn't click the button that starts your swf loading or you did click that button and your swf did load but isn't added to the display list. Or, it did load and it is added to the display list but it's not visible because something is over it or it's positioned off-stage or its alpha is 0 or its visible property is false. Or, maybe the swf was loaded and added to the display list and would have been visible but something occured before the next frame rendered to make the loaded swf not visible.

    There are almost always a lot of reasons that code can seem to "not work".  To find the problem, you should start by pinpointing exactly where your code fails.

    So, let's debug this code which should load test.swf and display it when btn is clicked:

    1. btn.addEventListener(MouseEvent.CLICK,btnF);

    function btnF(e:MouseEvent):void {

           var loader:Loader = new Loader();

           loader.load(new URLRequest("test.swf"));

           addChild(loader);

    }

    If you do not see what you expect, first confirm that what you expect to see is reasonable by clicking test.swf or its embedding html to confirm that you can see something on stage and thereby judge that test.swf is displayed.  Or, use the trace() function in test.swf to judge whether it is loaded or not.

    Once you do that you should make sure that code executes. Depending on where that code is located, it might not execute. To check if that code is executing you could use:

    trace("ok");

    1. btn.addEventListener(MouseEvent.CLICK,btnF);

    function btnF(e:MouseEvent):void {

           var loader:Loader = new Loader();

           loader.load(new URLRequest("test.swf"));

    }

    When you test that code you should see ok in the output panel (or that code is not executing or you disabled trace output.)  If that code is not executing (i.e., you do not see ok in the output panel), you need to fix that: 

    1. If that code is timeline code, you can conclude it is attached to a frame that has not played at the time you click btn.
    2. If it is in a class, that class has not been instantiated. 
    3. And/or that listener is added within a function that needs to be called.

    Assuming you see ok in the output panel, the next step would be to confirm your btn listener function (btnF) is being called:

    1. btn.addEventListener(MouseEvent.CLICK,btnF);

    function btnF(e:MouseEvent):void {

           trace(e);

           var loader:Loader = new Loader();

           loader.load(new URLRequest("test.swf"));

           addChild(loader);

    }

    After clicking btn you should see a MouseEvent.CLICK event in your output panel ([MouseEvent type="click" etc]). If you do not, btn is not being clicked. You might think you are clicking btn but Flash does not.

    There are quite a few ways that can happen but because there is no error message you know Flash is finding something with reference btn. So, btn exists. It is just not the object you are clicking.

    You should check for all btn objects by using Movie Explorer. If you find more than one, that may be the problem. Create a test fla and confirm multiple btn objects are the problem by removing all but the one closest to frame one of the parent timeline and retesting your code. If that works, re-add the other keyframes that contain btn (without dragging anything from the library) and the error should not return.

    If you do see [MouseEvent type="click" etc] in the output panel and there are no error messages, you know test.swf exists in a valid path (otherwise, Flash would generate an "Error #2044: Unhandled IOErrorEvent:. text=Error #2035: URL Not Found" message. But, you can add an Event.COMPLETE listener with a trace() function to confirm that test.swf is loading.

    If none of the above reveal the problem, then you know test.swf is loading. 

    To find why it is not being displayed you will need to execute code some milliseconds after loading is complete to see if loader is still on stage and visible some milliseconds after loading is complete or whether some other code is moving or removing or otherwise changing loader so its content is no longer visible.

    The code below should do that allowing you to check if loader is being used to load something else or its alpha or x or visible or some other property is unexpected.

    1. btn.addEventListener(MouseEvent.CLICK,btnF);

    var loader:Loader = new Loader();

    var t:Timer = new Timer(100,1);

    1. t.addEventListener(TimerEvent.TIMER,traceF);

    function btnF(e:MouseEvent):void {

           loader.load(new URLRequest("test.swf"));

           addChild(loader);

           t.start()

    }

    function traceF(e:TimerEvent):void{

           // The url property should show test.swf confirming loader is not being used to   // load something other than test.swf .

           // The numChildren property should be one greater than loader's childIndex or     // something is being positioned over loader

           trace(loader.content.loaderInfo.url,loader.parent.numChildren,loader.parent.getChildIndex (loader))

           var xml:XML = describeType(loader);

           var xmlList:XMLList = xml.accessor;

           for(var i:int=0;i<xmlList.length();i++){

                  if(xmlList[i].@access.indexOf("read")>-1){

                         // This will reveal most of the properties of loader and their                           // values (like alpha, x, y, visible etc).

                         trace(xmlList[i].@name,loader[xmlList[i].@name])

                  }

           }

    }

    You will need to check those properties and values carefully.  While it would obviously be a problem if a property like visible is false there are less obvious ways a property change can cause a problem.  For example, if you are loading a swf that contains only non-embedded classic text and you rotate your loader or its content, you will not see anything on stage from the loaded swf.

     

    Errors Caused by Asynchronous Code Execution


    Most code in flash executes in an orderly easy-to-follow way.  If you have

    f1();  // where functions f1() and f2() are defined in this scope

    f2();

    you can be sure the code in f1() will execute before the code in f2().  That is an example of synchronous code execution:  If statement1 precedes statement2, statement1 executes before statement2 .  Most actionscript executes synchronously.

    But some code executes asynchronously.  In particular, file loading is asynchronous and load-complete listener functions execute asynchronously.  For example,

    var urlLoader:URLLoader = new URLLoader();

    1. urlLoader.load(new URLRequest("test.txt"));  // test.txt should be in the same file with your flash files

    trace(urlLoader.data); // undefined because loading is not complete

    and

    var dataS:String;

    var urlLoader:URLLoader = new URLLoader();

    1. urlLoader.addEventListener(Event.COMPLETE,completeF);
    2. urlLoader.load(new URLRequest("test.txt"));

    function completeF(e:Event):void{

           dataS = urlLoader.data;

    }

    trace(urlLoader.data); // undefined because this executes before completeF() executes

    trace(dataS);  // null because dataS is typed but no value is assigned until completeF() executes

    To remedy, you should use listener functions to trigger code that depends on loaded assets:

    var dataS:String;

    var urlLoader:URLLoader = new URLLoader();

    1. urlLoader.addEventListener(Event.COMPLETE,completeF);
    2. urlLoader.load(new URLRequest("test.txt"));

    function completeF(e:Event):void{

           dataS = urlLoader.data;

           dependentCodeF();

    }

    function dependentCodeF():void{

           trace(dataS);

           // etc

    }

     

    gotoAndPlay and gotoAndStop (to a Frame Not Yet Loaded)


    If you use either of those and find the Flash is not going to the correct frame, after checking for typos, you should ensure the target frame is loaded.  This is more likely to occur when testing online but I've seen the same issue when testing locally, too.

    To remedy, don't execute the goto until the needed frame is loaded.  It's easiest to use a complete listener to ensure all frames are loaded if you encounter this problem:

    Example 1.   goto is applied to the swf that contains your code

    1. this.loaderInfo.addEventListener(Event.COMPLETE,completeF);

    function completeF(e:Event):void{

           this.gotoAndStop(this.totalFrames);

    }

    Example 2.  The goto is applied to a loaded swf:

    var loader:Loader = new Loader();

    1. loader.contentLoaderInfo.addEventListener(Event.COMPLETE,completeF);
    2. loader.load(new URLRequest("test.swf"));

    function completeF(e:Event):void{

           var mc:MovieClip = MovieClip(loader.content); // the loader's content property, after loading is complete, is the loaded swf's main timeline (which is a MovieClip).  Unfortunately, the flash compiler (in strict mode) often loses track of what object is of what class type and triggers a

           mc.gotoAndStop(mc.totalFrames);

    }

     

    Lost Object References


    There are many ways this occurs but most commonly it occurs in for-loops and while-loops.  One reference is used repeatedly so with each loop iteration the current object reference overwrites the previous object reference.  At the end of the for-loop that reference only refers to the last created object. Here's the typical problem (already mentioned in chapter 1):

    for (var io:int = 0; io < 4; io++) {

        var opBtn:Btn_operator = new Btn_operator();

    opBtn.addEventListener(MouseEvent.CLICK, pressOperator);

    }

    And then later in the code

    • opBtn.mouseEnabled = false;  //  only one opBtn instance will be disabled

    Among the many ways to resolve this issue you can,

    1. assign a name property to each object and use the getChildByName() method applied to the objects' parent:

    for (var i:int=0;i<4;i++) {

           var opBtn:Btn_operator = new Btn_operator();

           opBtn.name=i.toString();

           addChild(opBtn);

    }

    // then you can use:

    for (i=0;i<4;i++) {

           var btn = getChildByName(i.toString());  // if you use an opBtn reference, use Appendix A to debug the Flash error

           btn.mouseEnabled = false;

    }

    1. push each object into an array and latter loop through the array:

    var opBtnA:Array = [];

    for (var i:int=0;i<4;i++) {

           var opBtn:Btn_operator = new Btn_operator();

           opBtnA.push(opBtn);

           addChild(opBtn);

    }

    // then you can use:

    for (i=0;i<opBtnA.length;i++) {

           opBtnA[i].mouseEnabled = false;

    }

    1. add each object to a parent that contains nothing but those objects and loop through the parents children:

    var opBtnParent:Sprite = new Sprite();

    addChild(opBtnParent);

    for (var i:int=0;i<4;i++) {

           var opBtn:Btn_operator = new Btn_operator();

           opBtnParent.addChild(opBtn);

    }

    // then you can use:

    for (i=0;i<opBtnParent.numChildren;i++) {

           Btn_operator(opBtnParent.getChildAt(i)).mouseEnabled = false;

    }

    1. use a different object reference in each loop:

    for (var i:int=0;i<4;i++) {

           this["opBtn"+i] = new Btn_operator();

    }

    // then you can use:

    for (i=0;i<4;i++) {

           this["opBtn"+i].mouseEnabled = false;

    }

     

    Repeated Execution Code


    This only happens when code is added to a timeline frame and that frame repeatedly plays.  For example, if the following code is attached to a frame that repeatedly executes you will find f() being executed more and more frequently.

    var t:Timer = new Timer(1000,0);

    1. t.addEventListener(TimerEvent.TIMER,f);
    2. t.start();

    function f(e:TimerEvent):void{

           trace(getTimer());

    }

    To remedy, use a boolean so that code only executes the first time the frame plays:

    var alreadyExecuted:Boolean;

    if (! alreadyExecuted) {

           alreadyExecuted=true;

           var t:Timer=new Timer(1000,0);

           t.addEventListener(TimerEvent.TIMER,f);

           t.start();

           function f(e:TimerEvent):void {

                  trace(getTimer());

           }

    }

    The above code is also an example of a run-away loop defined by an accelerating loop.  If you use setInterval(), you can prevent it from triggering run-away loops by clearing the interval prior to each setting:

    var testI:int;

    clearInterval(testI);

    testI = setInterval(testF,1000);

    function testF(){

           trace(getTimer());

    }

    or enclose in a boolean. Or, even better, do both:

     

    var alreadyExecuted:Boolean;

    if (! alreadyExecuted) {

           var testI:int;

           clearInterval(testI);

           testI = setInterval(testF,1000);

           function testF(){

                  trace(getTimer());

           }

    }

     

    Problems Related to Class File Use


    1. To see the results of a changed class file, you must save your changes and then test or publish a new swf.
    2. If, in your default directory, you have a class file C1.as:

    package {

           public class C1 {

                  public function C1() {

                         trace("C1");

                  }

           }

    }

    And in a subdirectory Sub of your default directory you have a different class file C1.as:

    package Sub{

           public class C1 {

                  public function C1() {

                         trace("C.C1");

                  }

           }

    }

    Then in any other class instantiating a C1 instance, even if Sub.C1 is imported into that class and no matter the location of the other class, the C1 class from the default directory will be used, not the C1 class from the Sub directory.