diff --git a/.actionScriptProperties b/.actionScriptProperties new file mode 100644 index 0000000..09f4441 --- /dev/null +++ b/.actionScriptProperties @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/.flexLibProperties b/.flexLibProperties new file mode 100644 index 0000000..a647170 --- /dev/null +++ b/.flexLibProperties @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5be24d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.DS_Store + +.settings +bin +bin-debug +html-template +target +.sourceMate +FlexUnitApplication.mxml +.FlexUnitSettings diff --git a/.project b/.project new file mode 100644 index 0000000..94e2bc2 --- /dev/null +++ b/.project @@ -0,0 +1,18 @@ + + + promise-as3 + + + + + + com.adobe.flexbuilder.project.flexbuilder + + + + + + com.adobe.flexbuilder.project.flexlibnature + com.adobe.flexbuilder.project.actionscriptnature + + diff --git a/README.md b/README.md index 0d60fc9..a47a639 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,92 @@ -# Promises for Actionscript 3 + -## Attention Developers +## About -All work is currently in the [develop branch](https://github.com/CodeCatalyst/promise-as3/tree/develop)
-A detailed README is also availble in the develop branch. +promise-as3 is an ActionScript 3.0 implementation of the [Promises/A+ Specification](https://github.com/promises-aplus/promises-spec). Its implementation is derived from the [promise.coffee](https://github.com/CodeCatalyst/promise.coffee) object oriented [CoffeeScript](http://coffeescript.org/) reference implementation. +It is fully asynchronous, ensuring that the `onFulfilled` and `onRejected` callbacks are not executed in the same turn of the event loop as the call to `then()` with which they were registered. -## Introduction +It supports foreign promises returned by callbacks as long as they support the standard Promise `then()` method signature. +## API -The repository provides a AS3 library of futures as defined by [CommonJS](http://wiki.commonjs.org/wiki/Promises). +Create a deferred: -A promise (aka [Future](http://en.wikipedia.org/wiki/Futures_and_promises)) is an object thats acts as a proxy for a result that my not be initially known, usually because the computation of its value has not yet completed. Developers *defer* processing by returning a promise to respond later. A **promise**is essentially a read-only version of the **deferred**response. + import com.codecatalyst.promise.Deferred; + + ... + + var deferred:Deferred = new Deferred(); -* `new Deferred()` - * can add callbacks - * can be resolved or rejected - * can **promise()**to let you know what happened -  - +Resolve that deferred: + + deferred.resolve( value ); -* Promise (accessed via `new Deferred().promise`) - * can add callbacks - * Can't resolve, so you know it's legit when it does - * Can check resolve status - * Can cancel (*only in AS3 version*) +Or, reject that deferred: -  + deferred.reject( reason ); -Popularized in the [jQuery](http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.js) javascript library, Deferred(s) are now available for AS3 developers. This library emulates the jQuery v1.7 feature set of futures: Deferred, Promise, and Callbacks. This library also supports two syntactical approaches familiar to either jQuery users or Actionscript/Flex developers. - -Flex developers often use components asynchronously: +Obtain the promise linked to that deferred to pass to external consumers: -* setTimeOut() -* setInterval() -* callLater() -* Timer -* Loader -* URLLoader -* HTTPService -* RemoteObject -* WebService -* NetStream -* Socket -* NetConnection -* SharedObject + import com.codecatalyst.promise.Promise; + + ... + + var promise:Promise = deferred.promise; -The above components, unfortunately, do not have a consistent mechanism in which developers may handle the asynchronous responses… until now! With the above components developers would use: +Add (optional) handlers to that promise: -* addEventListener() -* Responders -* AsyncToken -* closures (aka callbacks) + promise.then( onFulfilled, onRejected ); -## Why Deferred(s) +## Internal Anatomy -Now developers can employ `Deferred.as` to build promises of responses. The interface is always the same regardless of the component/mechanism used to fulfill the promise. +This implementation decomposes Promise functionality into three classes: -The biggest advantages are +### Promise -1. Consistent, intuitive API for attaching handlers to asynchronous processes -2. Ability to intercept and transform results before handlers are called -3. Ability to chain futures in sequence -4. Ability to process futures in parallel (aka batch processing) +Promises represent a future value; i.e., a value that may not yet be available. +A Promise's `then()` method is used to specify `onFulfilled` and `onRejected` callbacks that will be notified when the future value becomes available. Those callbacks can subsequently transform the value that was resolved or the reason that was rejected. Each call to `then()` returns a new Promise of that transformed value; i.e., a Promise that is resolved with the callback return value or rejected with any error thrown by the callback. - +### Deferred -## Learning Resources -  +A Deferred is typically used within the body of a function that performs an asynchronous operation. When that operation succeeds, the Deferred should be resolved; if that operation fails, the Deferred should be rejected. -* [Crockford on JavaScript - Act III: Function the Ultimate](http://www.youtube.com/watch?feature=player_detailpage&v=ya4UHuXNygM#t=2529s) -* [Deferreds into jQuery](http://danheberden.com/presentations/jqsummit-deferreds-in-jquery/) -* [Deferreds - Putting Laziness to Work](http://danheberden.com/presentations/deferreds-putting-laziness-to-work/#1) -* [Creating Responsive Applications Using jQuery Deferred and Promises](http://msdn.microsoft.com/en-us/scriptjunkie/gg723713.aspx) -* [The Power Of Closures - Deferred Object Bindings In jQuery](http://www.bennadel.com/blog/2125-The-Power-Of-Closures-Deferred-Object-Bindings-In-jQuery-1-5.htm) -* [Fun with jQuery Deferred](http://intridea.com/2011/2/8/fun-with-jquery-deferred?blog=company) -* [Understanding jQuery.Deferred and Promise](http://joseoncode.com/2011/09/26/a-walkthrough-jquery-deferred-and-promise/) -* [A Graphical Explanation Of Javascript Closures In A jQuery Context](http://www.bennadel.com/blog/1482-A-Graphical-Explanation-Of-Javascript-Closures-In-A-jQuery-Context.htm) -* [From callbacks... to $.Deferred... to $.Callbacks](http://demo.creative-area.net/jqcon2011-boston/#1) -* [Using Promises to bind Button clicks](http://jsfiddle.net/ThomasBurleson/RTLr6/) -* [Demystifying jQuery 1.7′s $.Callbacks](http://addyosmani.com/blog/jquery-1-7s-callbacks-feature-demystified/) +Once a Deferred has been resolved or rejected, it is considered to be complete and subsequent calls to `resolve()` or `reject()` are ignored. + +Deferreds are the mechanism used to create new Promises. A Deferred has a single associated Promise that can be safely returned to external consumers to ensure they do not interfere with the resolution or rejection of the deferred operation. + +### Resolver + +Resolvers are used internally by Deferreds and Promises to capture and notify callbacks, process callback return values and propogate resolution or rejection to chained Resolvers. + +Developers never directly interact with a Resolver. + +A Resolver captures a pair of optional `onResolved` and `onRejected` callbacks and has an associated Promise. That Promise delegates its `then()` calls to the Resolver's `then()` method, which creates a new Resolver and schedules its delayed addition as a chained Resolver. + +Each Deferred has an associated Resolver. A Deferred delegates `resolve()` and `reject()` calls to that Resolver's `resolve()` and `reject()` methods. The Resolver processes the resolution value and rejection reason, and propogates the processed resolution value or rejection reason to any chained Resolvers it may have created in response to `then()` calls. Once a chained Resolver has been notified, it is cleared out of the set of chained Resolvers and will not be notified again. + +## Reference and Reading + +* [Common JS Promises/A Specification](http://wiki.commonjs.org/wiki/Promises/A) +* [Promises/A+ Specification](https://github.com/promises-aplus/promises-spec) +* [You're Missing the Point of Promises](https://gist.github.com/3889970) + +## Acknowledgements + +* [Kris Zyp](https://github.com/kriszyp), who proposed the original [Common JS Promises/A Specification](http://wiki.commonjs.org/wiki/Promises/A) and created [node-promise](https://github.com/kriszyp/node-promise) and [promised-io](https://github.com/kriszyp/promised-io); +* [Domenic Denicola](https://github.com/domenic) for the [Promises/A+ Specification](https://github.com/promises-aplus/promises-spec) and [Promises/A+ Compliance Test Suite](https://github.com/promises-aplus/promises-tests), and for his work with: +* [Kris Kowal](https://github.com/kriskowal), who created [q](https://github.com/kriskowal/q), a JavaScript promise library that pioneered many of the practices now codified in the [Promises/A+ Specification](https://github.com/promises-aplus/promises-spec); +* [Brian Cavalier](https://github.com/briancavalier) for his contributions to the [Promises/A+ Specification](https://github.com/promises-aplus/promises-spec) and [Promises/A+ Compliance Test Suite](https://github.com/promises-aplus/promises-tests), and the inspiration that [avow.js](https://github.com/briancavalier/avow) and [when.js](https://github.com/cujojs/when) (with [John Hann](https://github.com/unscriptable)) and [past GitHub issue discussions](https://github.com/cujojs/when/issues/60) have provided; +* [Shaun Smith](https://github.com/darscan), who wrote [a similar AS3 port](https://gist.github.com/4519372) of promise.coffee; +* [Jason Barry](http://dribbble.com/artifactdesign), who designed the promise-as3 logo. + +## License + +Copyright (c) 2013 [CodeCatalyst, LLC](http://www.codecatalyst.com/) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/promise-as3-logo.png b/promise-as3-logo.png new file mode 100644 index 0000000..9b68116 Binary files /dev/null and b/promise-as3-logo.png differ diff --git a/src/com/codecatalyst/promise/Deferred.as b/src/com/codecatalyst/promise/Deferred.as new file mode 100644 index 0000000..3dbaf5f --- /dev/null +++ b/src/com/codecatalyst/promise/Deferred.as @@ -0,0 +1,95 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2013 CodeCatalyst, LLC - http://www.codecatalyst.com/ +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +package com.codecatalyst.promise +{ + /** + * A Deferred is typically used within the body of a function that performs + * an asynchronous operation. When that operation succeeds, the Deferred + * should be resolved; if that operation fails, the Deferred should be rejected. + * + * Deferreds are the mechanism used to create new Promises. A Deferred has a + * single associated Promise that can be safely returned to external consumers + * to ensure they do not interfere with the resolution or rejection of the + * deferred operation. + */ + public class Deferred + { + // ======================================== + // Public properties + // ======================================== + + /** + * Promise of the future value of this Deferred. + */ + public function get promise():Promise + { + return resolver.promise; + } + + // ======================================== + // Protected properties + // ======================================== + + /** + * Internal Resolver for this Deferred. + */ + protected var resolver:Resolver; + + // ======================================== + // Constructor + // ======================================== + + public function Deferred() + { + super(); + + this.resolver = new Resolver(); + } + + // ======================================== + // Public methods + // ======================================== + + /** + * Resolve this Deferred with the specified value. + * + * Once a Deferred has been resolved, it is considered to be complete + * and subsequent calls to resolve() or reject() are ignored. + */ + public function resolve( value:* ):void + { + resolver.resolve( value ); + } + + /** + * Reject this Deferred with the specified error. + * + * Once a Deferred has been rejected, it is considered to be complete + * and subsequent calls to resolve() or reject() are ignored. + */ + public function reject( error:* ):void + { + resolver.reject( error ); + } + } +} \ No newline at end of file diff --git a/src/com/codecatalyst/promise/Promise.as b/src/com/codecatalyst/promise/Promise.as new file mode 100644 index 0000000..96da796 --- /dev/null +++ b/src/com/codecatalyst/promise/Promise.as @@ -0,0 +1,68 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2013 CodeCatalyst, LLC - http://www.codecatalyst.com/ +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +package com.codecatalyst.promise +{ + /** + * Promises represent a future value; i.e., a value that may not yet be available. + */ + public class Promise + { + // ======================================== + // Protected properties + // ======================================== + + /** + * Internal Resolver for this Promise. + */ + protected var resolver:Resolver; + + // ======================================== + // Constructor + // ======================================== + + public function Promise( resolver:Resolver ) + { + this.resolver = resolver; + } + + // ======================================== + // Public methods + // ======================================== + + /** + * Used to specify onFulfilled and onRejected + * callbacks that will be notified when the future value becomes + * available. + * + * Those callbacks can subsequently transform the value that was + * resolved or the error that was rejected. Each call to then() + * returns a new Promise of that transformed value; i.e., a Promise + * that is resolved with the callback return value or rejected with + * any error thrown by the callback. + */ + public function then( onFullfilled:Function = null, onRejected:Function = null ):Promise + { + return resolver.then( onFullfilled, onRejected ); + } + } +} \ No newline at end of file diff --git a/src/com/codecatalyst/promise/Resolver.as b/src/com/codecatalyst/promise/Resolver.as new file mode 100644 index 0000000..3cdb36b --- /dev/null +++ b/src/com/codecatalyst/promise/Resolver.as @@ -0,0 +1,279 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2013 CodeCatalyst, LLC - http://www.codecatalyst.com/ +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +package com.codecatalyst.promise +{ + import com.codecatalyst.util.nextTick; + + /** + * Resolvers are used internally by Deferreds and Promises to capture and + * notify callbacks, process callback return values and propogate resolution + * or rejection to chained Resolvers. + * + * Developers never directly interact with a Resolver. + * + * A Resolver captures a pair of optional onResolved and onRejected + * callbacks and has an associated Promise. That Promise delegates its + * then() calls to the Resolver's then() method, which creates a new + * Resolver and schedules its delayed addition as a chained Resolver. + * + * Each Deferred has an associated Resolver. A Deferred delegates resolve() + * and reject() calls to that Resolver's resolve() and reject() methods. + * The Resolver processes the resolution value and rejection reason, and + * propogates the processed resolution value or rejection reason to any + * chained Resolvers it may have created in response to then() calls. Once + * a chained Resolver has been notified, it is cleared out of the set of + * chained Resolvers and will not be notified again. + */ + internal class Resolver + { + // ======================================== + // Public properties + // ======================================== + + /** + * Promise of the future value of this Resolver. + */ + public function get promise():Promise + { + return _promise; + } + + // ======================================== + // Protected properties + // ======================================== + + /** + * Backing variable for promise. + */ + protected var _promise:Promise; + + /** + * Callback to execute when this Resolver's future value is resolved. + */ + protected var onResolved:Function; + + /** + * Callback to execute when this Resolver's future value is rejected. + */ + protected var onRejected:Function; + + [ArrayElementType("com.codecatalyst.promise.Resolver")] + /** + * Pending chained resolvers. + */ + protected var pendingResolvers:Array; + + /** + * Indicates whether this Resolver has been processed. + */ + protected var processed:Boolean; + + /** + * Indicates whether this Resolver has been completed. + */ + protected var completed:Boolean; + + /** + * The completion action (i.e. 'resolve' or 'reject'). + */ + protected var completionAction:String; + + /** + * The completion value (i.e. resolution value or rejection error). + */ + protected var completionValue:*; + + // ======================================== + // Constructor + // ======================================== + + public function Resolver( onResolved:Function = null, onRejected:Function = null ) + { + /** + * Default rejection handler, used when none is specified in order + * to propagate errors to chained Resolvers. + */ + function defaultRejectionHandler( error:* ):void + { + throw error; + } + + this.onResolved = onResolved; + this.onRejected = onRejected; + + this._promise = new Promise( this ); + this.pendingResolvers = []; + this.processed = false; + this.completed = false; + this.completionAction = null; + this.completionValue = undefined; + + if ( ! ( this.onRejected != null ) ) + this.onRejected = defaultRejectionHandler; + } + + // ======================================== + // Public methods + // ======================================== + + /** + * Resolve this Resolver with the specified value. + * + * Once a Resolver has been resolved, it is considered to be complete + * and subsequent calls to resolve() or reject() are ignored. + */ + public function resolve( result:* ):void + { + if ( !processed ) + process( onResolved, result ); + } + + + /** + * Reject this Resolver with the specified value. + * + * Once a Resolver has been rejected, it is considered to be complete + * and subsequent calls to resolve() or reject() are ignored. + */ + public function reject( error:* ):void + { + if ( !processed ) + process( onRejected, error ); + } + + /** + * Used to specify onResolved and onRejected + * callbacks that will be notified when the future value becomes + * available. + * + * Those callbacks can subsequently transform the value that was + * resolved or the error that was rejected. Each call to then() + * returns a new Promise of that transformed value; i.e., a Promise + * that is resolved with the callback return value or rejected with + * any error thrown by the callback. + */ + public function then( onResolved:Function = null, onRejected:Function = null ):Promise + { + if ( onResolved != null || onRejected != null ) + { + var pendingResolver:Resolver = new Resolver( onResolved, onRejected ); + + function schedulePendingResolver():void + { + schedule( pendingResolver ); + } + + nextTick( schedulePendingResolver ); + + return pendingResolver.promise; + } + + return promise; + } + + // ======================================== + // Protected methods + // ======================================== + + /** + * Propage the completion value to any pending Resolvers. + */ + protected function propagate():void + { + for each ( var pendingResolver:Resolver in pendingResolvers ) + { + pendingResolver[ completionAction ]( completionValue ); + } + } + + /** + * Schedule a Resolver as a pending Resolver, to be notified in a + * future event loop tick when the future value becomes available. + */ + protected function schedule( pendingResolver:Resolver ):void + { + pendingResolvers.push( pendingResolver ); + + if ( completed ) + propagate(); + } + + /** + * Completes this Resolver. + */ + protected function complete( action:String, value:* ):void + { + onResolved = onRejected = null; + + completionAction = action; + completionValue = value; + completed = true; + + propagate(); + } + + /** + * Completes this Resolver a resolved result. + */ + protected function completeResolved( result:* ):void + { + complete( 'resolve', result ); + } + + /** + * Complets this Resolver with a rejection error. + */ + protected function completeRejected( error:* ):void + { + complete( 'reject', error ); + } + + /** + * Processes the resolved value or rejection error using the + * specified callback. + */ + protected function process( callback:Function, value:* ):void + { + processed = true; + + try + { + if ( callback != null ) + value = callback( value ); + + if ( value != null && value.then is Function ) + { + value.then( completeResolved, completeRejected ); + } + else + { + completeResolved( value ); + } + } + catch ( error:* ) + { + completeRejected( error ); + } + } + } +} \ No newline at end of file diff --git a/src/com/codecatalyst/util/nextTick.as b/src/com/codecatalyst/util/nextTick.as new file mode 100644 index 0000000..1a301ab --- /dev/null +++ b/src/com/codecatalyst/util/nextTick.as @@ -0,0 +1,155 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2013 CodeCatalyst, LLC - http://www.codecatalyst.com/ +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +package com.codecatalyst.util +{ + /** + * Executes the specified callback function on the next turn of the event loop. + */ + public function nextTick( callback:Function, parameters:Array = null ):void + { + CallbackQueue.instance.schedule( callback ); + } +} + +import flash.utils.clearInterval; +import flash.utils.setInterval; + +/** + * Used to queue callbacks for execution on the next turn of the event loop (using a single timer). + * + * @private + */ +class CallbackQueue +{ + // ======================================== + // Public properties + // ======================================== + + /** + * Singleton instance accessor. + */ + public static const instance:CallbackQueue = new CallbackQueue(); + + // ======================================== + // Protected properties + // ======================================== + + /** + * Queued Callback(s). + */ + protected const queuedCallbacks:Array = []; + + /** + * Interval identifier. + */ + protected var intervalId:int = 0; + + // ======================================== + // Constructor + // ======================================== + + public function CallbackQueue() + { + super(); + } + + // ======================================== + // Public methods + // ======================================== + + /** + * Add a callback to the end of the queue, to be executed on the next turn of the event loop. + */ + public function schedule( closure:Function, parameters:Array = null ):void + { + queuedCallbacks.push( new Callback( closure, parameters ) ); + + if ( queuedCallbacks.length == 1 ) + intervalId = setInterval( execute, 0 ); + } + + // ======================================== + // Protected methods + // ======================================== + + /** + * Execute any queued callbacks and clear the queue. + */ + protected function execute():void + { + clearInterval( intervalId ); + + for each ( var queuedCallback:Callback in queuedCallbacks ) + { + queuedCallback.execute(); + } + + queuedCallbacks.length = 0; + } +} + +/** + * Used to capture a callback closure, along with optional parameters. + * + * @private + */ +class Callback +{ + // ======================================== + // Protected properties + // ======================================== + + /** + * Callback closure. + */ + protected var closure:Function; + + /** + * Callback parameters. + */ + protected var parameters:Array; + + // ======================================== + // Constructor + // ======================================== + + public function Callback( closure:Function, parameters:Array = null ) + { + super(); + + this.closure = closure; + this.parameters = parameters; + } + + // ======================================== + // Public methods + // ======================================== + + /** + * Execute this callback. + */ + public function execute():void + { + closure.apply( null, parameters ); + } +} \ No newline at end of file diff --git a/src/design.xml b/src/design.xml new file mode 100644 index 0000000..eed62e6 --- /dev/null +++ b/src/design.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/manifest.xml b/src/manifest.xml new file mode 100644 index 0000000..41d099f --- /dev/null +++ b/src/manifest.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file