Wednesday, March 9, 2011

Cancelling inflight remote calls

So here is a couple of scenarios:
  1. User clicks search, then immediately clicks logout, a couple of moments later the search results come back and the user is taken back into search results screen, resulting in an unstable app now that the user is null
  2. Sales Rep is working on a quote for customer A, in the middle of an operation (search, save, quote), they get a phone call from customer B and swithes accounts to handle the phone call. Want to make sure that customer A result events don't corrupt or popuplate customer B's data model.
So how to cancel a remote operation?

mx.rpc.Operation does have a cancel method, but that won't work if you are using a Responder pattern based on a token. Besides, it requires knowledge of all of the operations that are inflight.

Here is another approach. Add a static variable to AsyncToken. When a new AsyncToken is created, copy that static variable into a local variable. Before calling our responders compare the local variable to the static variable before proceeding. If you need to cancel a response, change the static variable on the AsyncToken.

So that is the solution... now how to implement it.
You need to extend 3 classes to get this work. AsyncToken, Operation, RemoteObject.
AsyncToken is extended as described above.
Operation is extended to return the new AsyncToken,
RemoteObject is extended to use the new Operation.

AsyncTokenWResponderValidation.as
package com.squaredi.remoting.cancelable
{
 import mx.core.mx_internal;
 import mx.messaging.messages.IMessage;
 import mx.rpc.AsyncToken;
 import mx.rpc.IResponder;
 import mx.rpc.events.FaultEvent;
 import mx.rpc.events.ResultEvent;
 
 use namespace mx_internal;
 /**
  * Context: You need to cancel an inflight message
  * Issue: While you can cancel an mx.rpc.Operation, it doesn't actually cancel responders assigned to the AsyncToken
  *     Since many of the frameworks relay on the Responder pattern, which applies the responder to the token,
  *     this is how we can not respond to an inflight message
  * 
  * Usage: if needing to cancel an inflight message -> AsyncTokenWResponderValidation.resetValidationToken();
  *       
  * 
  * 
  * */
 public dynamic class AsyncTokenWResponderValidation extends AsyncToken
 {
  
  private static var globalValidationToken:String;
  protected var validationToken:String;
  
  public static function resetValidationToken():void
  {
   globalValidationToken = String(new Date().time);
  }
  
  public static function getGlobalValidationToken():String
  {
   return globalValidationToken;
  }
  
  public function AsyncTokenWResponderValidation(message:IMessage=null)
  {
   super(message);
   if (getGlobalValidationToken() == null)
   {
    resetValidationToken()
   }
   validationToken = getGlobalValidationToken();
  }
  
  
  mx_internal override function applyFault(event:FaultEvent):void
  {
   if (isTokenValid())
   {
    super.applyFault(event);
   }
  }

  mx_internal override function applyResult(event:ResultEvent):void
  {
   if (isTokenValid())
   {
    super.applyResult(event);
   }
  }

  protected function isTokenValid():Boolean
  {
   return this.validationToken == getGlobalValidationToken() 
  }

 }
}

CancelableOperation.as
package com.squaredi.remoting.cancelable
{
 import mx.core.mx_internal;
 import mx.messaging.messages.IMessage;
 import mx.rpc.AbstractService;
 import mx.rpc.AsyncToken;
 import mx.rpc.remoting.Operation;
 
 use namespace mx_internal;
 
 public class CancelableOperation extends Operation
 {
  public function CancelableOperation(remoteObject:AbstractService=null, name:String=null)
  {
   super(remoteObject, name);
  }
  
  mx_internal override function invoke(message:IMessage, token:AsyncToken=null):AsyncToken
  {
   if (token == null)
   {
    token = new AsyncTokenWResponderValidation(null);
   }
   return super.invoke(message,token);
  }
 }
}

CancelableRemoteObject.as
package com.squaredi.remoting.cancelable
{
 import mx.core.mx_internal;
 import mx.rpc.AbstractOperation;
 import mx.rpc.remoting.RemoteObject;
 
 use namespace mx_internal
 public class CancelableRemoteObject extends RemoteObject
 {
  public function CancelableRemoteObject(destination:String=null)
  {
   super(destination);
  }
  
  override public function getOperation(name:String):AbstractOperation
  {
   var op:AbstractOperation = super.getOperation(name);
   if (!( op is CancelableOperation))
   {
    op = new CancelableOperation(this, name);
    this._operations[name] = op;
    op.asyncRequest = this.asyncRequest;
   }
   return op;
  }
 }
}

No comments:

Post a Comment