actionscript-3

Best way to handle multiple URLLoader Requests in AS3


I have written the following class to abstract multiple calls to my server for sending data to a database and getting that data back.

package {
import flash.net.URLRequest;
import flash.net.URLLoader;
import flash.events.*;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.net.URLVariables;
import flash.net.URLRequestMethod;
import flash.net.URLLoaderDataFormat;


public class sqlReq extends URLLoader {

    private var phpVars: URLVariables;
    private var phpFileRequest: URLRequest;
    private var phpLoader: URLLoader;
    private var tempPhpVars: String;
    private var totalBytes: uint;
    
    public function sqlReq(link: String, dataVars: String) {
        // constructor code

        phpVars = new URLVariables();
        phpFileRequest = new URLRequest(link);
        phpFileRequest.method = URLRequestMethod.POST;
        phpLoader = new URLLoader();


        phpLoader.dataFormat = URLLoaderDataFormat.TEXT;
        phpLoader.addEventListener(Event.COMPLETE, phpReqCompleted);
        phpLoader.addEventListener(IOErrorEvent.IO_ERROR, phpReqError);
        phpLoader.addEventListener(IOErrorEvent.NETWORK_ERROR, phpReqNetworkError);
        phpLoader.addEventListener(ProgressEvent.PROGRESS, phpReqprogress);
        //phpLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, phpReqErrorhttpStatus);


        phpFileRequest.data = dataVars;
        phpLoader.load(phpFileRequest);
    }


    private function phpReqCompleted(event: Event): void {

        //trace("returned vars",event.target.data);
        tempPhpVars = event.target.data;
        this.dispatchEvent(new Event(Event.COMPLETE));
        clearEvents();

    }

    private function phpReqError(event: IOErrorEvent): void {
        this.dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR));

        tempPhpVars = "resultIs:" + event.target.data;
        //trace("Code Confirmed: " + event);

    }
    public function phpReqResults(): String {
        return tempPhpVars;
    }
    public function ByteAmount(): uint {
    
        return totalBytes;
        
    }
    private function phpReqprogress(event: ProgressEvent): void {
        //trace(event.bytesLoaded, "total ", event.bytesTotal);
        totalBytes = event.bytesTotal;
        this.dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS));

    }
    private function phpReqNetworkError(event: IOErrorEvent): void {
        this.dispatchEvent(new IOErrorEvent(IOErrorEvent.NETWORK_ERROR));
        //phpVars = event.target.data;
        //trace("Code Confirmed: " + event);
    }
    private function clearEvents(): void {
        phpLoader.removeEventListener(Event.COMPLETE, phpReqCompleted);
        phpLoader.removeEventListener(IOErrorEvent.IO_ERROR, phpReqError);
        phpLoader.removeEventListener(IOErrorEvent.NETWORK_ERROR, phpReqNetworkError);
        phpLoader.removeEventListener(ProgressEvent.PROGRESS, phpReqprogress);
        //phpLoader.removeEventListener(HTTPStatusEvent.HTTP_STATUS, phpReqErrorhttpStatus);
        phpVars = null;
        phpLoader = null;
        phpFileRequest = null;

    }
}
    }

The code has been working flawlessly, until recently, I noticed something strange. For example, when I make multiple requests back to back, the second sql request gets the data of the previous sql request. If I create timer and wait, everything is fine, but I do need to be able to send multiple requests and I should expect different results for the different requests.

And these are the calls I make in the app (I do import all necessary classes so the code runs fine). The main issue: once in a while (more often than not), getName would return null and getAge would work, other times, getAge would return null and getName would work. If I call these functions separately, they ALWAYS work. Is there anything I am missing.

    var sql:sqlReq;

    var myTimer: Timer = new Timer(500, 20);

    myTimer.addEventListener(TimerEvent.TIMER, onTick);
    myTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimeUp);

    function getName():void{
        sql= new sqlReq("http://mysite.php","&user=Sarah");
        sql.addEventListener(Event.COMPLETE,gotMyName);
        sql.addEventListener(IOErrorEvent.IO_ERROR,ErrorGettingMyName);
    }
    function getAge():void{
        sql= new sqlReq("http://mysite.php","&age=32");
        sql.addEventListener(Event.COMPLETE,gotMyAge);
        sql.addEventListener(IOErrorEvent.IO_ERROR,ErrorGettingMyAge);
    }
    function gotMyName(event:Event):void{
       //All good
    }
     function gotMyAge(event:Event):void{
       //All good
    }
    function ErrorGettingMyName(event:IOErrorEvent):void{
       //All bad
    }
    function ErrorGettingMyAge(event:IOErrorEvent):void{
       //All bad
    }

    function onTick(event:TimerEvent):void{
    
        getName();
        getAge();
    }
    function onTimeUp(event:TimerEvent):void{
         //clear and remove timer events
    }

Solution

  • If I were to devise such a system, I'd do the following (there might be some errors in the code since I stopped doing AS3 some 7 years ago, but the logic of how it supposed to be should be solid so I believe in you figuring it all out):

    First, I wouldn't put the network request itself into the constructor, but rather into a separate method. Why? Flash is asynchronous, you never know WHEN the events fire, better safe than sorry:

            phpLoader.addEventListener(ProgressEvent.PROGRESS, phpReqprogress);
            //phpLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, phpReqErrorhttpStatus);
        }
    
        public function request(): void
        {
            phpFileRequest.data = dataVars;
            phpLoader.load(phpFileRequest);
        }
    

    Then, the Array to keep the requests while they are running:

    var SQLs: Array = new Array;
    
    function isActive(sql: sqlReq): Boolean
    {
        var index: int = SQLs.indexOf(sql);
        
        return index > -1;
    }
    
    // Single gate to delete the sqlReq instance.
    function removeRequest(sql: sqlReq, completeHandler: Function, errorHandler: Function): void
    {
        var index: int = SQLs.indexOf(sql);
        
        if (index < 0)
        {
            return;
        }
        
        SQLs.splice(index, 1);
        
        // Unsubscribe from events.
        sql.removeEventListener(Event.COMPLETE, completeHandler);
        sql.removeEventListener(IOErrorEvent.IO_ERROR, errorHandler);
    }
    

    So, when you create new requests, you do as following:

    function getName(): void
    {
        var sql: sqlReq = new sqlReq("http://mysite.php","&user=Sarah");
        
        // Store the instance.
        SQLs.push(sql);
        
        // Subscribe for the events.
        sql.addEventListener(Event.COMPLETE, gotMyName);
        sql.addEventListener(IOErrorEvent.IO_ERROR, ErrorGettingMyName);
        
        // Run the request.
        sql.request();
    }
    

    Then, in (all) the handlers you need to do:

    function gotMyName(event: Event): void
    {
        // Get reference to the author of the event.
        var sql: sqlReq = event.target as sqlReq;
        
        // Check if it is an active request and not some rogue.
        if (!isActive(sql))
        {
            // Do not process the request if you cannot identify it.
            return;
        }
    
        // All good.
        // ...
        // Your code here.
        
        // Put the request to the rest.
        removeRequest(sql, gotMyName, ErrorGettingMyName);
    }