4

I'm trying to do something exceedingly simple: write a function that reads text from a text file and returns the text in a string using AS3.

The Function

    public function readData(path:String):String
    {
        var dataSet:String;

        var urlRequest:URLRequest = new URLRequest(path);
        var urlLoader:URLLoader = new URLLoader();
        urlLoader.dataFormat = URLLoaderDataFormat.TEXT;
        urlLoader.addEventListener(Event.COMPLETE, urlLoader_complete);
        urlLoader.load(urlRequest);

        function urlLoader_complete(evt:Event):void {
            dataSet = urlLoader.data;
            trace(dataSet)
        }
        trace(dataSet);
        return dataSet;
    }

Calling the Function

    var dataString:String = aq.readData("http://example.com/data.txt");
    trace(dataString);

This code returns a null string when I run it. Why?

EDIT: Ok, I now see that this doesn't work because urlLoader is acting asynchronously. I'm writing a program that reads in a data file and acts on it. Does this mean that I need to write the rest of my program inside function urlLoader_complete? Or should I pause the program until urlLoader is finished?

terrace
  • 674
  • 2
  • 9
  • 19
  • promote `urlLoader_complete()` to be a class function instead of a local function, then use it to continue your program. You can use callbacks, events, or `Signals` (https://github.com/robertpenner/as3-signals) to notify your other code that the load has completed – divillysausages Mar 24 '12 at 11:45

2 Answers2

3

In Flash and Flex, all network I/O is asynchronous. It has to be this way in order to avoid blocking your browser.

As a result, it is not possible to write a readData() function that directly returns the result of a network read operation. You will have to pass a callback function to the readData() function. When readData() has finished reading the data, it can call the callback function.

For example:

/**
 * Asynchronous function to read data as a string. When the data has been read,
 * the callback function is called with the result.
 * 
 * @param path     the URL to read
 * @param callback the function that is called with the result; should take
 *                 one string argument.
 */
public function readData(path:String, callback:Function):void
{
    var dataSet:String;

    var urlRequest:URLRequest = new URLRequest(path);
    var urlLoader:URLLoader = new URLLoader();
    urlLoader.dataFormat = URLLoaderDataFormat.TEXT;
    urlLoader.addEventListener(Event.COMPLETE, urlLoader_complete);
    urlLoader.load(urlRequest);

    function urlLoader_complete(evt:Event):void {
        dataSet = urlLoader.data;
        trace(dataSet);
        callback(dataSet);
    }
}

Here is how you might call that function from Flex:

<mx:Label id="mylabel" />
<mx:Button click="readData('http://www.google.com/',
    function(s:String):void {mylabel.text = s})" />
Mike Morearty
  • 9,188
  • 5
  • 29
  • 34
  • Hey Mike, turns out your suggestion didn't give me what I needed because I'm looking to create a sequence of actions. Take a look at my edits above. – terrace Dec 14 '09 at 09:12
  • 2
    This is one of the trickiest things about ActionScript programming (and JavaScript programming, for that matter, which has the same issue): Some things are asynchronous, and there is really nothing you can do to prevent that. The only thing you can do is adjust to that async model. In my sample, you would write the next step of your program inside the callback() function. If you want to indicate to the user that he should wait, you could change the cursor (google "flex wait cursor") or draw a spinner or display the word "Loading..." or something. – Mike Morearty Dec 14 '09 at 17:54
  • I supposed I could do a: while(dataSet == null) { if(dataSet != null) { break; } else { trace("loading data") } }? – terrace Dec 15 '09 at 04:37
  • You can't do a while (dataSet == null) loop because that might cause the application to freeze or even cause the function to time out if it takes a long time to load the data. Also, I'm pretty sure that the actual data loading is not running in a separate thread, meaning that the data would not be able to load while your function is still executing. – Cameron Dec 19 '09 at 06:26
  • Yeah, I found that out the hard way. Now I have a cascade of functions that trigger each other through even listeners. Surely there must be a better way to do this (though perhaps not with ActionScript). – terrace Dec 26 '09 at 06:27
1

Its been 3 years ago since this question arose with you, but since I stumbled on this problem a few hours ago, and managed to get it to work and thought why not share it. There might be better alternatives already, but hey I just started coding Actionscript so no blames :)

First build a Preloader class with a filecounter. There will be a numFiles parameter in the constructor which holds the total number of files to be loaded. Every time when the complete method is called, 1 to the filecounter will be added and a statement will be checking if all files are loaded. when the numFiles is equal to the counter call the start() method of the calling class.

*Preloader.as *

package
{
    import flash.display.Loader;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.net.URLLoader;
    import flash.net.URLRequest;

    public class Preloader
    {
        public var urlLoader:URLLoader;
        public var response:Array = new Array();
        public var callingClass:Object;
        public var numFiles:uint;
        private var counter:uint;

        public function Preloader(callingClass:Object, numfiles:uint)
        {
            this.callingClass = callingClass;
            this.numFiles = numFiles;
        }

        public function load(name:String):void
        {
            var request:URLRequest = new URLRequest(name);
            urlLoader = new URLLoader();
            urlLoader.addEventListener(Event.COMPLETE, onLoad);
            urlLoader.load(request);
        }

        public function onLoad(event:Event):void
        {
            response[counter] = event.currentTarget.data;
            if(numFiles == counter) {
                callingClass.start();
            } else {
                counter++;
            }
        }
    }
}

The constructor method in the calling class will have to call all the preload files and the start method will be the replacement of your constructor stuff. note that when the preloader loads it need the reference to its calling class and the total number of "to be loaded" files:

package  
{
    import flash.display.MovieClip;
    import misc.Preloader;

    public class Path extends MovieClip
    {
        public var preloader:Preloader = new Preloader(this, 3); //pass this class and the total number of files

        public function Controller()
        {       
            preloader.loadJSON('file1.js');
            preloader.loadJSON('file2.js');
            preloader.loadJSON('file3.js');
        }

        public function start():void
        {
            trace(preloader.response[0]); //Get first file contents
        }

    }
}
Chris Visser
  • 1,512
  • 10
  • 23
  • Hah! Thanks for answering. Three years later I'm just learning node.js and I've run into the exact same problem. This time it's different, however, as there is a wealth of information online about async vs. sync programming and how to avoid the dreaded "callback pyramid", not to mention tons of libraries to handle the problem. – terrace Mar 24 '12 at 17:55