Skip to content

Commit

Permalink
Complete overhaul of the project; as3-async now deals with providing …
Browse files Browse the repository at this point in the history
…a clean and compact `Promise` implementation.

Source, Test coverage and Examples for the project.  Need to continue
work on the documentation and update the README.
  • Loading branch information
jonnyreeves committed Feb 27, 2012
1 parent fb85f13 commit 87688be
Show file tree
Hide file tree
Showing 15 changed files with 1,062 additions and 75 deletions.
39 changes: 1 addition & 38 deletions README.mkd
@@ -1,41 +1,4 @@
AS3 Async
=========

Utilities for working with asynchronous code in ActionScript 3. This library is open source and participation from
others is encouraged! :)

when
----
Provides a convenient way of handling Events using closures. All mappings are automatically cleaned up by default,
but they can be made to persist by setting the `oneShot` argument to `false`.

```actionscript
package app.service {
public class DataLoader extends EventDispatcher {
private var _url : String;
private var _parser : IDataParser;
public function DataLoader(url : String, parser : IDataParser) {
_url = url;
_parser = parser;
}
public function load() : void {
const urlLoader : URLLoader = new URLLoader();
// Create a mapping that will listen for Event.COMPLETE being dispatched by urlLoader.
when(urlLoader, Event.COMPLETE, function () : void {
// This code is invoked when the event is fired, because we are inside a closure
// it can access properties from the parent function and parent Class.
const data : MyData = _parser.parse(urlLoader.data);
// In this example we dispatch a custom Event to let the rest of the system know
// something has happened.
dispatchEvent(new DataLoadedEvent(DataLoadedEvent.DATA_LOADED, data));
});
urlLoader.load(new URLRequest(_url));
}
}
}
```
A clean and compact implementation of the [Promise pattern](http://en.wikipedia.org/wiki/Futures_and_promises).
13 changes: 13 additions & 0 deletions examples/Main.as
@@ -0,0 +1,13 @@
package
{
import org.osflash.async.DeferredEventDispatcherExample;

import flash.display.Sprite;
/**
* @author jonny
*/
public class Main extends Sprite {
new DeferredEventDispatcherExample();
//new BenchmarkWhen();
}
}
48 changes: 48 additions & 0 deletions examples/org/osflash/async/BenchmarkWhen.as
@@ -0,0 +1,48 @@
package org.osflash.async {
import org.osflash.async.Promise;
import org.osflash.async.mocks.SimpleDeferred;
import org.osflash.async.when;

import flash.utils.getTimer;
import flash.utils.setTimeout;
/**
* @author jonny
*/
public class BenchmarkWhen
{
private var REPS : int = 250000;
private var DEF_DELAY : int = 10000;

private var _startTime : uint;
private var _promises : Array;

public function BenchmarkWhen()
{

setTimeout(run, 2000);
}

private function run() : void {
setup();

_startTime = getTimer();
trace("Deferreds Created: " + _startTime);

const promise : Promise = when.apply(null, _promises);
promise.completes(function() : void {
const totalTime : Number = getTimer() - _startTime;
trace("Benchmark complete in: " + totalTime + ", when execution time: " + (totalTime - DEF_DELAY).toString());
});
}

private function setup() : void
{
_promises = [];
for (var i : uint = 0; i < REPS; i++) {
_promises.push(new SimpleDeferred(true, DEF_DELAY));
}
}


}
}
53 changes: 53 additions & 0 deletions examples/org/osflash/async/DeferredEventDispatcherExample.as
@@ -0,0 +1,53 @@
package org.osflash.async
{
import org.osflash.async.impl.TweetVO;
import org.osflash.async.impl.XMLTweetParser;

import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.net.URLLoader;
import flash.net.URLRequest;

/**
* @author Jonny
*/
public class DeferredEventDispatcherExample
{
public function DeferredEventDispatcherExample()
{
when(fetchTweetsFor("catburton"), fetchTweetsFor("jonnyreeves"))
.completes(function() : void {
trace("Got Tweets yo!");
})
.completes(onTweetsFetched)
.fails(onFailed);
}

private function fetchTweetsFor(username : String) : Promise
{
const urlLoader : URLLoader = new URLLoader();
const deferred : DeferredEventDispatcher = new DeferredEventDispatcher(urlLoader)
.resolveOn(Event.COMPLETE, function(event : Event) : Vector.<TweetVO> {
return new XMLTweetParser().parse(urlLoader.data);
})
.rejectOn(IOErrorEvent.IO_ERROR);

urlLoader.load(new URLRequest("https://api.twitter.com/1/statuses/user_timeline.xml?screen_name=" + username + "&count=3"));
return deferred.promise();
}

private function onFailed(error : Error) : void
{
trace("Failed to fetch tweets: " + error);
}

private function onTweetsFetched(outcomes : Array) : void
{
trace("Fetched " + outcomes.length + " sets of Tweets");

for each (var tweets : Vector.<TweetVO> in outcomes) {
trace(tweets.join("\n"));
}
}
}
}
16 changes: 16 additions & 0 deletions examples/org/osflash/async/impl/TweetVO.as
@@ -0,0 +1,16 @@
package org.osflash.async.impl
{
/**
* @author Jonny
*/
public class TweetVO
{
public var id : String;
public var message : String;

public function toString() : String
{
return message;
}
}
}
24 changes: 24 additions & 0 deletions examples/org/osflash/async/impl/XMLTweetParser.as
@@ -0,0 +1,24 @@
package org.osflash.async.impl
{
/**
* @author Jonny
*/
public class XMLTweetParser
{
public function parse(data : *) : Vector.<TweetVO>
{
const xml : XML = new XML(data);
const result : Vector.<TweetVO> = new Vector.<TweetVO>();

for each (var status : XML in xml..status) {
const tweet : TweetVO = new TweetVO();
tweet.id = status.id;
tweet.message = status["text"];

result.push(tweet);
}

return result;
}
}
}
159 changes: 159 additions & 0 deletions src/org/osflash/async/Deferred.as
@@ -0,0 +1,159 @@
package org.osflash.async
{
/**
* A Deferred object provides the means to fulfil a Promise by the means of calling 'resolve', 'reject' and
* 'progress'. Typlically a Deferred object is instantiated and retained by a client so that when the operation
* completes, rejects, or progresses, the appropriate methods can be invoked. The client can invoke 'abort' if
* the Deferred operation needs to be terminated (in which case none of the callback will be invoked.)
*
* A Deferred object uses a finite state machine to ensure that it can only transition from PENDING to one of
* either RESOLVED or REJECTED to ensure that the Promise gets resolved in a predicable fashion.
*
* @author Jonny Reeves.
*/
public class Deferred implements Promise
{
private static const PENDING : uint = 0;
private static const RESOLVED : uint = 1;
private static const REJECTED : uint = 2;
private static const ABORTED : uint = 3;

private const _completeListeners : Vector.<Function> = new Vector.<Function>();
private const _failListeners : Vector.<Function> = new Vector.<Function>();
private const _progressListeners : Vector.<Function> = new Vector.<Function>();

private var _finalCallback : Function;
private var _state : uint = PENDING;
private var _outcome : *;

/**
* Notifies all 'completes' handlers that the deferred operation was succesful. An optional outcome object
* can be supplied which will provided to all the complete handlers.
*
* @parm outcome The optional result of the Deferred operation.
*/
public function resolve(outcome : * = null) : void
{
if (_state != PENDING) {
return;
}

_outcome = outcome;
_state = RESOLVED;

const len : uint = _completeListeners.length;
for (var i : uint = 0; i < len; i++) {
_completeListeners[i](_outcome);
}

clearListeners();
invokeFinalCallback();
}

/**
* Notifies all 'fails' handlers that this deferred operation has been unsuccesful. The supplied Error object
* will be supplied to all of the handlers.
*
* @param error Error object which explains how or why the operation was unsuccesful.
*/
public function reject(error : Error) : void
{
if (_state != PENDING) {
return;
}

// By contact, we will always supply an Error object to the fail handlers.
_outcome = error || new Error("Promise Rejected");
_state = REJECTED;

const len : uint = _failListeners.length;
for (var i : uint = 0; i < len; i++) {
_failListeners[i](_outcome);
}

clearListeners();
invokeFinalCallback();
}

/**
* Notifies all of the 'progresses' handlers of the current progress of the deferred operation. The supplied
* value should be a Number between 0 and 1 (although there is no fixed validation). Once the deferred
* operation has resolved further progress updates will be ignored.
*
* @param ratioComplete A number between 0 and 1 which represents the progress of the deferred oepration.
*/
public function progress(ratioComplete : Number) : void
{
const len : uint = _progressListeners.length;
for (var i : uint = 0; i < len; i++) {
_progressListeners[i](ratioComplete);
}
}

/**
* Aborts the deferred operation; none of the handlers bound to the Promise will be invoked; typically this
* is used when the Deferred's host needs to cancel the operation.
*/
public function abort() : void
{
_state = ABORTED;
_outcome = null;
_finalCallback = null;

clearListeners();
}

public function completes(callback : Function) : Promise
{
if (_state == PENDING) {
_completeListeners.push(callback);
}
else if (_state == RESOLVED) {
callback(_outcome);
}

return this;
}

public function fails(callback : Function) : Promise
{
if (_state == PENDING) {
_failListeners.push(callback);
}
else if (_state == REJECTED) {
callback(_outcome);
}

return this;
}

public function progresses(callback : Function) : Promise
{
if (_state == PENDING) {
_progressListeners.push(callback);
}

return this;
}

public function thenFinally(callback : Function) : void
{
_finalCallback = callback;
}

private function clearListeners() : void
{
_completeListeners.length = 0;
_failListeners.length = 0;
_progressListeners.length = 0;
}

private function invokeFinalCallback() : void
{
if (_finalCallback !== null) {
_finalCallback();
_finalCallback = null;
}
}
}
}

0 comments on commit 87688be

Please sign in to comment.