/**
* Copyright devon_o ( http://wonderfl.net/user/devon_o )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/qxZA
*/
package
{
import com.bit101.components.PushButton;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.utils.setTimeout;
import flash.text.TextField;
/**
* A look at promises in AS3
*
* Some reading
* http://en.wikipedia.org/wiki/Futures_and_promises
* http://blogs.msdn.com/b/ie/archive/2011/09/11/asynchronous-programming-in-javascript-with-promises.aspx
* https://gist.github.com/darscan/4519372
*
* @author devon o.
*/
[SWF(width='465', height='465', backgroundColor='#000000', frameRate='60')]
public class Main extends Sprite
{
private var game:Game;
private var assets:Assets;
private var player:Player;
private var startButton:PushButton;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(event:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
this.game = new Game();
this.assets = new Assets();
this.player = new Player();
initOutputText();
initStartButton();
}
private function initOutputText():void
{
var tf:TextField = new TextField();
tf.width = stage.stageWidth;
tf.autoSize = TextFieldAutoSize.LEFT;
tf.multiline = true;
tf.wordWrap = true;
tf.defaultTextFormat = new TextFormat("_sans", 11, 0x000000);
tf.x = tf.y = 10;
addChild(tf);
Utils.output = tf;
}
private function initStartButton():void
{
this.startButton = new PushButton(this, 300, 10, "Start Sequence", startLoadSequence);
}
private function startLoadSequence(event:MouseEvent):void
{
this.startButton.enabled = false;
Utils.output.text = "";
// The beauty of the Promise construct: Asynchronous event/object chaining in crystal clarity.
this.assets.getAssets()
.then( this.player.getPlayerInfo )
.then( this.game.getHighScores )
.then( this.game.init )
.then( onLoadSequenceComplete, onLoadSequenceError );
}
/**
* Called after successful completion of entire sequence
*/
private function onLoadSequenceComplete(arg:*):void
{
Utils.addOutputText("Load sequence complete. Starting game.");
this.game.start();
this.startButton.enabled = true;
}
/**
* Called if any step of the sequence fails
*/
private function onLoadSequenceError(err:Error):void
{
Utils.addOutputText("Load sequence failed. Reason = " + err.message);
this.startButton.enabled = true;
}
}
}
import flash.utils.setTimeout;
class Assets
{
/**
* Class to simulate the loading of external assets
*/
private var graphicsDef:Deferred;
private var soundsDef:Deferred;
public function Assets() { }
public function getAssets():Promise
{
// A nested Promise
return getGraphics()
.then(getSounds)
.then(onAssetLoadSuccess, onAssetLoadErr);
}
private function getGraphics():Promise
{
Utils.addOutputText("Loading game graphics...");
this.graphicsDef = new Deferred(onGraphicsLoadSuccess, onGraphicsLoadErr);
// Here you would create a loader instance and load your graphics.
// a Loader error (http, security, io, etc.) would call rejectGraphics
// a Loader success would call resolveGraphics
var f:Function = Utils.randomFunction(resolveGraphics, rejectGraphics);
// simulate asynchronous loading time
var time:int = int(Utils.randRange(3000, 500));
setTimeout(f, time);
return this.graphicsDef;
}
private function getSounds(arg:* = null):Promise
{
Utils.addOutputText("Loading game sounds...");
this.soundsDef = new Deferred(onSoundsLoadSuccess, onSoundsLoadErr);
// Here you would create a loader instance and load your sounds.
// a Loader error (http, security, io, etc. would call rejectSounds
// a Loader success would call resolveSounds
var f:Function = Utils.randomFunction(resolveSounds, rejectSounds);
// simulate asynchronous loading time
var time:int = int(Utils.randRange(3000, 500));
setTimeout(f, time);
return this.soundsDef;
}
private function rejectGraphics():void
{
this.graphicsDef.reject(new Error("Could not load graphics"));
}
private function resolveGraphics():void
{
this.graphicsDef.resolve(null);
}
private function onGraphicsLoadSuccess(arg:* = null):void
{
Utils.addOutputText("Graphics loaded successfully.");
}
private function onGraphicsLoadErr(err:Error):void
{
throw(err);
}
private function rejectSounds():void
{
this.soundsDef.reject(new Error("Could not load sounds"));
}
private function resolveSounds():void
{
this.soundsDef.resolve(null);
}
private function onSoundsLoadSuccess(arg:* = null):void
{
Utils.addOutputText("Sounds loaded successfully.");
}
private function onSoundsLoadErr(err:Error):void
{
throw(err);
}
private function onAssetLoadSuccess(arg:* = null):GameDataObject
{
var gdo:GameDataObject = new GameDataObject();
gdo.assets = this;
return gdo;
}
private function onAssetLoadErr(err:Error):void
{
throw(err);
}
}
class Game
{
/**
* Class to simulate a Game object
*/
public function Game() { }
private var playerInfoDef:Deferred;
private var highScoreDef:Deferred;
public function getHighScores(gdo:GameDataObject):Promise
{
Utils.addOutputText("Loading high score data for PlayerID: " + gdo.player.playerID + "...");
this.highScoreDef = new Deferred(onHighScoreLoadSuccess, onHighScoreLoadErr);
// Here you would create a loader instance and load your high scores from a database or whatever.
// a Loader error (http, security, io, etc.) would call rejectScores
// a Loader success would call resolveScores
var f:Function = Utils.randomFunction(resolveScores, rejectScores);
// simulate asynchronous loading time
var time:int = int(Utils.randRange(3000, 500));
setTimeout(f, time, gdo);
return this.highScoreDef;
}
private function rejectScores(gdo:GameDataObject):void
{
this.highScoreDef.reject(new Error("Could not load high scores"));
}
private function resolveScores(gdo:GameDataObject):void
{
this.highScoreDef.resolve(gdo);
}
private function onHighScoreLoadSuccess(gdo:GameDataObject):GameDataObject
{
Utils.addOutputText("High score data loaded successfully");
gdo.game = this;
return gdo;
}
private function onHighScoreLoadErr(err:Error):void
{
throw(err);
}
public function init(gdo:GameDataObject):void
{
Utils.addOutputText("Game is initializing with " + gdo.assets + ", " + gdo.game + ", " + gdo.player);
}
public function start():void
{
Utils.addOutputText("Game is starting.");
}
}
class Player
{
/**
* Class to simulate a Player object
*/
public var playerID:String;
private var playerInfoDef:Deferred;
public function Player() { }
public function getPlayerInfo(gdo:GameDataObject):Promise
{
Utils.addOutputText("Loading Player Info data...");
this.playerInfoDef = new Deferred(onPlayerInfoLoadSuccess, onPlayerInfoLoadErr);
// Here you would create a loader instance and load your player data.
// a Loader error (http, security, io, etc.) would call rejectPlayerInfo
// a Loader success would call resolvePlayerInfo
var f:Function = Utils.randomFunction(resolvePlayerInfo, rejectPlayerInfo);
// simulate asynchronous loading time
var time:int = int(Utils.randRange(3000, 500));
setTimeout(f, time, gdo);
return this.playerInfoDef;
}
private function rejectPlayerInfo(gdo:GameDataObject):void
{
this.playerInfoDef.reject(new Error("Could not load player info"));
}
private function resolvePlayerInfo(gdo:GameDataObject):void
{
this.playerInfoDef.resolve(gdo);
}
private function onPlayerInfoLoadSuccess(gdo:GameDataObject):GameDataObject
{
Utils.addOutputText("Player Info data loaded successfully");
this.playerID = "12345";
gdo.player = this;
return gdo;
}
private function onPlayerInfoLoadErr(err:Error):void
{
throw(err);
}
}
class GameDataObject
{
public var game:Game;
public var player:Player;
public var assets:Assets;
}
import flash.text.TextField;
class Utils
{
/**
* Some helper methods
*/
public static var output:TextField;
public static function randomFunction(succeedFunc:Function, failFunc:Function, chanceOfFailure:Number = .20):Function
{
return Math.random() < chanceOfFailure ? failFunc : succeedFunc;
}
public static function randRange(max:Number, min:Number = 0, decimals:int = 0):Number
{
if (min > max) return NaN;
var rand:Number = Math.random() * (max - min) + min;
var d:Number = Math.pow(10, decimals);
return ~~((d * rand) + 0.5) / d;
}
public static function addOutputText(text:String):void
{
var str:String = output.text;
str += text + "\n------------\n";
output.text = str;
}
}
/**
* Everything below here from: https://gist.github.com/darscan/4519372
*/
class Deferred implements Promise
{
private const pending:Array = [];
private var processed:Boolean;
private var completed:Boolean;
private var completionAction:String;
private var completionValue:*;
private var onResolved:Function;
private var onRejected:Function;
public function Deferred(onResolved:Function = null, onRejected:Function = null)
{
this.onResolved = onResolved;
this.onRejected = onRejected || throwError;
}
public function resolve(result:*):void
{
processed || process(onResolved, result);
}
public function reject(error:*):void
{
processed || process(onRejected, error);
}
public function then(onResolved:Function = null, onRejected:Function = null):Promise
{
if (onResolved != null || onRejected != null)
{
const deferred:Deferred = new Deferred(onResolved, onRejected);
NextTick.call(schedule, [deferred]);
return deferred;
}
return this;
}
private function throwError(error:*):void
{
throw error;
}
private function schedule(deferred:Deferred):void
{
pending.push(deferred);
completed && propagate();
}
private function propagate():void
{
for each (var deferred:Deferred in pending)
deferred[completionAction](completionValue);
pending.length = 0;
}
private function complete(action:String, value:*):void
{
onResolved = null;
onRejected = null;
completionAction = action;
completionValue = value;
completed = true;
propagate();
}
private function completeResolved(result:*):void
{
complete('resolve', result);
}
private function completeRejected(error:*):void
{
complete('reject', error);
}
private function process(closure:Function, value:*):void
{
processed = true;
try
{
closure && (value = closure(value));
value is Promise
? Promise(value).then(completeResolved, completeRejected)
: completeResolved(value);
}
catch (error:*)
{
completeRejected(error);
}
}
}
import flash.display.Sprite;
import flash.events.Event;
class NextTick
{
private static const SPR:Sprite = new Sprite();
private static const Q:Array = [];
public static function call(closure:Function, args:Array = null):void
{
Q.push(new Scope(closure, args));
Q.length == 1 && SPR.addEventListener(Event.ENTER_FRAME, run);
}
private static function run(e:Event):void
{
SPR.removeEventListener(Event.ENTER_FRAME, run);
for each (var scope:Scope in Q.splice(0))
scope.execute();
}
}
class Scope
{
private var _closure:Function;
private var _args:Array;
public function Scope(closure:Function, args:Array)
{
_closure = closure;
_args = args;
}
public function execute():void
{
_args ? _closure.apply(null, _args) : _closure();
}
}
interface Promise
{
function then(onResolved:Function = null, onRejected:Function = null):Promise;
}