Search code examples
flashactionscript-3urlloader

Flash AS3 - URLLoader loads last data again


Okay, here is one weird bug...

In my app, I load multiple xml files. And to load those, I always create a new URLLoader. Nothing special.

The first file works fine, the second one does, too. The third one, however, reports to have loaded fine, but the data it holds is actually the data of the second file. The current workaround is to load the third file twice, which somehow works... weird.

I have absolutely no clue how such a thing can happen. All three files are of course different files with different paths.

Here is the relevant class, hope it is of any use, if something is unclear, please ask. The DownloadJob class is merely a helper class that holds a String and a Function object. The latter one is called when the download is finished.

    // Actual class stuff
    private var _downloadQueue  :Array = new Array();

    /**
     * Adds a download to the queue. Will be started immediatly.
     * @param   p_url       The URL of the download.
     * @param   p_callback  The function to call when the download is finished. Has to take a DisplayObject/Sound/String as first parameter.
     */
    public function addDownload(p_url:String, p_callback:Function) :void
    {
        var job :DownloadJob = new DownloadJob(p_url, p_callback);
        _downloadQueue.push(job);

        debug.ttrace("added: " + job.url);

        // If it is the only item, start downloading
        if (_downloadQueue.length == 1)
        {
            var job :DownloadJob = DownloadJob(_downloadQueue[0]);
            load(job);
        }
    }

    /**
     * Will call the callback and dispatch an event if all loading is done.
     * @param   p_event The event that is passed when a download was completed.
     */
    private function downloadComplete(p_event:Event) :void 
    {
        var job :DownloadJob = DownloadJob(_downloadQueue[0]);
        var downloaded :Object = p_event.target;
        _downloadQueue.splice(0, 1);

        debug.ttrace("completed: " + job.url);

        // Notify via callback
        if (downloaded is LoaderInfo)
        {
            job.callback(downloaded.content);
        }
        else if (downloaded is Sound)
        {
            job.callback(downloaded);
        }
        else if (downloaded is URLLoader)
        {
            // This one holds the data of the previously loaded xml, somehow
            job.callback(URLLoader(downloaded).data);
        }

        // Continue downloading if anything is left in the queue
        if (_downloadQueue.length > 0)
        {
            var job :DownloadJob = DownloadJob(_downloadQueue[0]);
            load(job);
        }
        else
        {
            dispatchEvent(new Event(EVENT_DOWNLOADS_FINISHED));
        }
    }

    /**
     * Will load the passed job immediatly.
     * @param   p_job   The job to load.
     */
    private function load(p_job:DownloadJob) :void
    {
        // Different loaders needed for data, sound and non-sound
        if (p_job.url.indexOf(".xml") != -1 ||
            p_job.url.indexOf(".txt") != -1)
        {
            var urlloader :URLLoader = new URLLoader();
            urlloader.addEventListener(Event.COMPLETE, downloadComplete);
            urlloader.addEventListener(IOErrorEvent.IO_ERROR, handleError);
            urlloader.load(new URLRequest(p_job.url));
        }
        else if (   p_job.url.indexOf(".mp3") != -1 &&
                    p_job.url.indexOf(".flv") != -1)
        {
            var sound :Sound = new Sound();
            sound.addEventListener(Event.COMPLETE, downloadComplete);
            sound.addEventListener(IOErrorEvent.IO_ERROR, handleError);
            sound.load(new URLRequest(p_job.url));
        }
        else
        {
            var loader :Loader = new Loader();
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, downloadComplete);
            loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, handleError);
            loader.load(new URLRequest(p_job.url));
        }
    }

}

Solution

  • I spent some time on your class and could not find any errors, even when dispatching random addDownload commands from a timer - everything went as expected, no mix-ups, no weird data residue. I can only guess that perhaps the issue has something to do with weird variable handling in ActionScript, if it really is within the code you posted at all.

    So then I took the liberty of rearranging your Class a bit:

    • I changed the queue's type to Vector.<DownloadJob>. This lets us get rid of all the type casting.
    • The current download is stored in the field variable currentJob. There will only be one job to work on at a time, anyway. This eliminates all the function arguments.
    • A job will only be added to the queue, if currentJob isn't null, i.e. a download is actually in progress. We don't have to queue things, if there's no need to wait. This leaves only a single call to each push() and splice(). No more uncertainties in respect to when things get added and removed.
    • Removed hungarian notation (we don't do that in ActionScript).
    • Split your larger methods into smaller, more readable chunks (helps me think :) ).
    • Removed all the comments, except for the API method (the code should speak for itself, I believe).

    Try to exchange the code below with your previous class and see if the problem still exists. If it does, I am pretty sure the problem exists outside of your loader, possibly in the callback functions that process the payload.

    private static const EVENT_DOWNLOADS_FINISHED : String = "EVENT_DOWNLOADS_FINISHED";
    private var currentJob : DownloadJob;
    private var downloadQueue : Vector.<DownloadJob> = new Vector.<DownloadJob> ();
    
    /**
     * Adds a download to the queue. Will be started immediatly.
     * @param   url       The URL of the download.
     * @param   callback  The function to call when the download is finished. Has to take a DisplayObject/Sound/String as first parameter.
     */
     public function addDownload ( url : String, callback : Function ) : void {
        var job : DownloadJob = new DownloadJob ( url, callback );
        if (currentJob) downloadQueue.push ( job );
        else {
            currentJob = job;
            load ();
        }
    }
    
    private function load () : void {
        if ( jobIsText () ) loadText ();
        else if ( jobIsSound () ) loadSound ();
    }
    
    private function jobIsText () : Boolean {
        var url : String = currentJob.url;
        return url.indexOf ( ".xml" ) != -1 || url.indexOf ( ".txt" ) != -1;
    }
    
    private function jobIsSound () : Boolean {
        var url : String = currentJob.url;
        return url.indexOf ( ".mp3" ) != -1;
    }
    
    private function loadText () : void {
        var urlloader : URLLoader = new URLLoader ();
        urlloader.addEventListener ( Event.COMPLETE, handleComplete );
        urlloader.addEventListener ( IOErrorEvent.IO_ERROR, handleError );
        urlloader.load ( new URLRequest ( currentJob.url ) );
    }
    
    private function loadSound () : void {
        var sound : Sound = new Sound ();
        sound.addEventListener ( Event.COMPLETE, handleComplete );
        sound.addEventListener ( IOErrorEvent.IO_ERROR, handleError );
        sound.load ( new URLRequest ( currentJob.url ) );
    }
    
    private function handleComplete ( ev : Event ) : void {
        processPayload ( ev.target );
    
        if (downloadQueue.length > 0) loadNext ();
        else dispatchEvent ( new Event ( EVENT_DOWNLOADS_FINISHED ) );
    }
    
    private function handleError ( ev : Event ) : void {
        trace ( "Error while downloading:" + currentJob.url );
    }
    
    private function processPayload ( loader : Object ) : void {
        currentJob.callback ( getPayload ( loader ) );
        currentJob = null;
    }
    
    private function loadNext () : void {
        currentJob = downloadQueue.splice ( 0, 1 )[0];
        load ();
    }
    
    private function getPayload ( loader : Object ) : Object {
        return (loader is LoaderInfo) ? loader.content :     
                   (loader is URLLoader) ? URLLoader ( loader ).data :
                       loader;
    }