Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: lib/action.js

Issue 10233013: Crawler, second version (Closed)
Patch Set: Created April 12, 2013, 1:38 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/skin/text-x-source.png ('k') | lib/action_platform.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/action.js
===================================================================
new file mode 100644
--- /dev/null
+++ b/lib/action.js
@@ -0,0 +1,882 @@
+/*
+ * We avoid using a "let" statement so that we can test this module with js-test-driver, which doesn't support
+ * non-standard JavaScript syntax.
+ */
+var x = require( "action_platform" );
+var Action_Platform = x.Action_Platform;
+
+/**
+ * @namespace The action library, working with both synchronous and asynchronous actions.
+ */
+Action = {};
+
+//-------------------------------------------------------
+// Platform functions
+//-------------------------------------------------------
+/*
+ * We forward the basic platform functions into this name space for ease of use and a single place for documentation.
+ */
+/**
+ * Dispatch a function into a future JavaScript thread. JS is single-threaded, so the function won't run concurrently
+ * with the present thread. Rather it creates a new JS call stack with the argument function at the root of the stack.
+ * The browser implementation is setTimeout( f, 0 ).
+ *
+ * @type {function(function)}
+ */
+Action.dispatch = Action_Platform.dispatch;
+
+//-------------------------------------------------------
+
+/**
+ * The common states of all actions. The ordinary start state is Ready leads to only three transitions: from Ready to
+ * Running, and from Running to both Done and Exception. For actions that are not fully initialized by their constructors,
+ * there's also the state Init and a transition to Ready.
+ * @enum {number}
+ */
+Action.State = {
+ /**
+ * An available start state for actions that use more then their constructors for initialization.
+ */
+ Init: 0,
+ /**
+ * The ordinary start state. An action is ready after it is fully initialized.
+ */
+ Ready: 1,
+ /**
+ * The subprogram of the action is currently running. The state is changed immediately upon the call to go() or run().
+ */
+ Running: 2,
+ /**
+ * The action completed without exception. In this case no catcher was called. The state is changed after the
+ * subprogram has finished and before calling the finisher.
+ */
+ Done: 3,
+ /**
+ * The action threw an exception. In this case any catcher specified would be called. The state is changed
+ * after the subprogram has finished and before calling the catcher.
+ */
+ Exception: 4
+};
+
+//-------------------------------------------------------
+// Action interfaces
+//-------------------------------------------------------
+/**
+ * The base action interface is just a marker.
+ *
+ * This interface declaration is in Closure Compiler syntax, but for use by IntelliJ IDEA. The compiler would optimize
+ * it out, but it ought to go into a separate file anyway.
+ *
+ * @interface
+ */
+Action.Action_interface = function()
+{
+ /**
+ * Every action is either reliable, which means that it's guaranteed to return control to the caller, or unreliable,
+ * which means no such guarantee exists. Unreliable does not mean "never returns"; what would be the point of that?
+ *
+ * Reliability is a self-declaration for primitive actions. For composite actions, that is, actions that have at least
+ * one other action within themselves, reliability can (often) be inferred.
+ *
+ * @expose
+ * @type {boolean}
+ */
+ this.reliable = null;
+};
+
+/**
+ *
+ * @interface
+ * @extends Action.Action_interface
+ */
+Action.Synchronous_Action_interface = function()
+{
+ /**
+ * Every synchronous action is, by definition, reliable, since it always returns control to its caller. The return
+ * of control can be either ordinary or exceptional, but that distinction is irrelevant to the meaning of "reliable".
+
+ * @type {boolean}
+ */
+ this.reliable = true;
+};
+
+/**
+ * The subprogram of a synchronous action is called 'run', to distinguish it from an asynchronous subprogram.
+ */
+Action.Synchronous_Action_interface.prototype.run = function()
+{
+};
+
+//-------------------------------------------------------
+/**
+ * @interface
+ * @extends Action.Action_interface
+ */
+Action.Asynchronous_Action_interface = function()
+{
+ /**
+ * The default for an asynchronous action is unreliable. While some asynchronous actions are reliable, its prudent not
+ * to assume that otherwise without specific knowledge.
+ *
+ * @type {boolean}
+ */
+ this.reliable = false;
+};
+
+Action.Asynchronous_Action_interface.prototype._go = function()
+{
+};
+
+
+//-----------------------------------------------------------------------------------------
+// Join Messaging
+//-----------------------------------------------------------------------------------------
+/*
+ * Join messages are part of the basic action implementation, because the behavior of a join requires augmenting the
+ * behavior of the finisher. Upon an action completing, it notifies other actions (directly or indirectly) that it has
+ * completed. Thus, in addition to the caller-designated finisher functions, there may be an additional set of calls
+ * made at this time.
+ *
+ * Some classes generate join messages, such as those that
+ * wait for another action to complete. Some accept join messages, just as Join_class itself. Yet others, however, the
+ * category of condition transformers, both generate and accept such messages, just as the gate for join-with-timeout.
+ * As a consequence, we need to define these interfaces separately, so that, for example, both join actions and
+ * condition transformers can each consistently accept messages
+ */
+
+/**
+ * Interface for a receiver of join messages.
+ * @interface
+ */
+Action.JM_Attentive = function()
+{
+};
+
+/**
+ * Receive a notice that a dependent condition has completed well.
+ *
+ * @param id
+ */
+Action.JM_Attentive.prototype.notice_good = function( id )
+{
+};
+
+/**
+ * Receive a notice that a dependent condition has completed badly.
+ *
+ * @param {*} id
+ * The identifier for the dependent condition in case there's more than one.
+ * @param {*} e
+ * An exception object as it appears in a catch clause.
+ */
+Action.JM_Attentive.prototype.notice_bad = function( id, e )
+{
+};
+
+/**
+ * Interface for a sender of join messages. This interface is required because reporters have state that may need to be
+ * queried by a receiver.
+ * @interface
+ */
+Action.JM_Reporting = function()
+{
+};
+
+/**
+ * Watch the ending of this action.
+ *
+ * @param {Action.JM_Attentive} watcher
+ * The watcher object.
+ * @param {*} their_id
+ * An opaque identifier by which the peer identifies the relation.
+ * @returns {*}
+ * Our identifier for the relation.
+ */
+Action.JM_Reporting.prototype.watch = function( watcher, their_id )
+{
+};
+
+//-------------------------------------------------------
+/**
+ * Base class implementation.
+ * @constructor
+ * @implements {Action.JM_Reporting}
+ * @implements {Action.Asynchronous_Action_interface}
+ */
+Action.Asynchronous_Action = function()
+{
+};
+
+/**
+ * We set up the finisher and catcher function at construction time. The design principle here is that flow of control
+ * is structural and should be set up at the beginning. In addition, it enables run() and go() to be parallel, both
+ * accepting arguments for the body of the action.
+ *
+ * @this {Action.Asynchronous_Action}
+ * @param {function} [finisher]
+ * @param {function} [catcher]
+ */
+Action.Asynchronous_Action.init = function( finisher, catcher )
+{
+ this.finisher = finisher;
+ this.catcher = catcher;
+
+ /**
+ * The common state of a asynchronous action
+ * @type {Action.State}
+ * @private
+ */
+ this._state = Action.State.Ready;
+
+ /**
+ * @type {Array.<{watcher,id}>}
+ */
+ this._end_watchers = [];
+
+ /**
+ * The value of an action, used to invoke the finisher as its array of arguments.
+ * @type {Array}
+ * @protected
+ */
+ this._argv = [];
+};
+
+Object.defineProperty( Action.Asynchronous_Action.prototype, "state", {
+ /**
+ * @this {Action.Asynchronous_Action}
+ */
+ get: function()
+ {
+ return this._state;
+ }
+} );
+
+Object.defineProperty( Action.Asynchronous_Action.prototype, "completed", {
+ /**
+ * @this {Action.Asynchronous_Action}
+ */
+ get: function()
+ {
+ return this._state >= Action.State.Done;
+ }
+} );
+
+Object.defineProperty( Action.Asynchronous_Action.prototype, "completed_well", {
+ /**
+ * @this {Action.Asynchronous_Action}
+ */
+ get: function()
+ {
+ return this._state == Action.State.Done;
+ }
+} );
+
+Object.defineProperty( Action.Asynchronous_Action.prototype, "exception", {
+ /**
+ * @this {Action.Asynchronous_Action}
+ */
+ get: function()
+ {
+ if ( this._state == Action.State.Exception )
+ {
+ return this._exception;
+ }
+ else
+ {
+ throw new Error( "Action is not in an exception state." );
+ }
+ }
+} );
+
+/**
+ * Start up the subprogram body for this action instance.
+ *
+ * Any arguments here are passed along unchanged to the action, which may or may not ignore them.
+ */
+Action.Asynchronous_Action.prototype.go = function()
+{
+ if ( this._state != Action.State.Ready )
+ {
+ throw new Error( "Call to go() is invalid because the action is not in state 'Ready'." );
+ }
+ this._state = Action.State.Running;
+ this._go.apply( this, arguments );
+};
+
+/**
+ * The default action body is to do nothing.
+ */
+Action.Asynchronous_Action.prototype._go = function()
+{
+};
+
+/**
+ * Cancellation is an ordinary end to an action. We halt execution of the action and end immediately.
+ *
+ * The analog of this method for exceptional end is abort().
+ *
+ * Design Note: Commands the force early end to an action only affect the action itself, at most. They do not affect
+ * the execution of finisher and catcher functions that the action may have been invoked with. Nor do they affect
+ * notices to join actions waiting on the early-terminated action. One of the motivations for this behavior is that we
+ * want reliable actions to remain reliable. If cancellation were to cause finishers, not to run, no action could be
+ * considered reliable.
+ */
+Action.Asynchronous_Action.prototype.cancel = function()
+{
+ this.end_well();
+};
+
+/**
+ * Abortion in an exceptional end to an action. We halt execution of the action and end immediately.
+ *
+ * The analog of this method for ordinary end is cancel(), which see for more commentary.
+ *
+ * @param {*} [e]
+ * An exception object to associate with exceptional termination. If absent, defaults to a new Error object.
+ */
+Action.Asynchronous_Action.prototype.abort = function( e )
+{
+ this.end_badly( e ? e : new Error( "Action aborted by external command." ) );
+};
+
+/**
+ * The default termination behavior is to do nothing.
+ *
+ * Actions that allocate resources should override this method and release their resources here. This method is always
+ * called when the action ends.
+ *
+ * @protected
+ */
+Action.Asynchronous_Action.prototype.terminate = function()
+{
+};
+
+/**
+ * Change state to Done and execute the finisher.
+ *
+ * @protected
+ */
+Action.Asynchronous_Action.prototype.end_well = function()
+{
+ function good()
+ {
+ this.watcher.notice_good( this.id );
+ }
+
+ /*
+ * We may only complete once.
+ */
+ if ( this.completed )
+ return;
+ /*
+ * Note that there's no exception handling in this function. In order to mimic the behavior of the try-finally
+ * statement, an exception thrown from a finisher is treated as if it had happened within a finally block, which is to
+ * say, it throws the exception. There's no need for extra code to do that.
+ *
+ * In addition, the state is left at Done if the finisher throws an exception. In this case, the exception does not
+ * come from the action itself, but from user code. So regardless of how the finisher terminates, it does not change
+ * that the action completed ordinarily.
+ */
+ this._state = Action.State.Done;
+ this.terminate();
+ this._each_watcher( good );
+ if ( this.finisher ) this.finisher.apply( null, this._argv );
+};
+
+/**
+ * Change state to Exception and execute the catcher followed by the finisher.
+ *
+ * @protected
+ * @param e
+ * An exception value
+ */
+Action.Asynchronous_Action.prototype.end_badly = function( e )
+{
+ function bad()
+ {
+ this.watcher.notice_bad( this.id, e );
+ }
+
+ /*
+ * We may only complete once.
+ */
+ if ( this.completed )
+ return;
+ /*
+ * In contrast to end_well(), this function does require a try-finally statement. If the catcher throws an
+ * exception, then we still have to execute the finisher anyway.
+ */
+ this._state = Action.State.Exception;
+ /**
+ * The object identified with the exceptional completion of the action.
+ *
+ * @type {*}
+ * @private
+ */
+ this._exception = e;
+ this.terminate();
+ this._each_watcher( bad );
+ try
+ {
+ this._argv.unshift( e );
+ if ( this.catcher ) this.catcher.apply( null, this._argv );
+ }
+ finally
+ {
+ this._argv.shift();
+ if ( this.finisher ) this.finisher.apply( null, this._argv );
+ }
+};
+
+/**
+ * Call a function on each watcher.
+ *
+ * @param {function} f
+ * A function to be called on the watcher structure.
+ * @private
+ */
+Action.Asynchronous_Action.prototype._each_watcher = function( f )
+{
+ for ( var j = 0 ; j < this._end_watchers.length ; ++j )
+ {
+ try
+ {
+ /**
+ * @type {{watcher:Action.JM_Attentive, id}}
+ */
+ var w = this._end_watchers[ j ];
+ if ( !w )
+ {
+ /*
+ * It's OK for a watcher to be null. All this means is that the watcher withdrew before completion.
+ */
+ continue;
+ }
+ f.call( w );
+ }
+ catch ( e )
+ {
+ /*
+ * The use of this catch block is a defense so that we can ignore exceptions. There shouldn't be any, though, but
+ * just in case.
+ */
+ }
+ }
+};
+
+/**
+ * Watch the ending of this action.
+ *
+ * @param {Action.JM_Attentive} watcher
+ * The watcher object.
+ * @param {*} their_id
+ * An opaque identifier by which the peer identifies itself.
+ * @returns {number}
+ * Our identifier, which is the index in the _end_watchers array.
+ */
+Action.Asynchronous_Action.prototype.watch = function( watcher, their_id )
+{
+ return this._end_watchers.push( { watcher: watcher, id: their_id } ) - 1;
+};
+
+//noinspection JSUnusedGlobalSymbols
+/**
+ * Withdraw a watcher
+ */
+Action.Asynchronous_Action.prototype.withdraw = function( our_id )
+{
+ this._end_watchers[ our_id ] = null;
+};
+
+//-------------------------------------------------------
+/**
+ * @interface
+ * @extends Action.Action_interface
+ */
+Action.Joinable = function()
+{
+};
+
+//-----------------------------------------------------------------------------------------
+// ACTIONS
+//-----------------------------------------------------------------------------------------
+
+//-------------------------------------------------------
+// Defer
+//-------------------------------------------------------
+/**
+ * Class constructor for Defer actions, which defer execution of a function (the "trial") until after the current
+ * JavaScript-thread has run to completion.
+ *
+ * @constructor
+ * @extends Action.Asynchronous_Action
+ */
+Action.Defer_class = function()
+{
+ /**
+ * @const
+ * @type {boolean}
+ */
+ this.reliable = true;
+};
+Action.Defer_class.prototype = new Action.Asynchronous_Action();
+
+/**
+ *
+ */
+Action.Defer_class.prototype._go = function()
+{
+ Action.dispatch( this._body.bind( this ) );
+};
+
+/**
+ * The deferred trial is run inside of a try-catch-finally statement.
+ * @private
+ */
+Action.Defer_class.prototype._body = function()
+{
+ try
+ {
+ if ( this.trial )
+ this._argv = this.trial();
+ }
+ catch ( e )
+ {
+ /*
+ * Note that because the trial did not return, it had no return value. Thus we have no context for a sensible
+ * return value argument for end_badly().
+ */
+ this.end_badly( e );
+ return;
+ }
+ this.end_well();
+};
+
+/**
+ * Instance constructor for standard Defer actions.
+ * @constructor
+ * @param f
+ * @param {function} [finisher]
+ * @param {function} [catcher]
+ */
+Action.Defer = function( f, finisher, catcher )
+{
+ Action.Asynchronous_Action.init.call( this, finisher, catcher );
+ this.trial = f;
+};
+Action.Defer.prototype = new Action.Defer_class();
+
+//-------------------------------------------------------
+/**
+ *
+ * @constructor
+ * @extends Action.Asynchronous_Action
+ */
+Action.Delay_class = function()
+{
+ /**
+ * Delay actions always complete, even if cancelled or aborted early.
+ * @const
+ * @type {boolean}
+ */
+ this.reliable = true;
+};
+Action.Delay_class.prototype = new Action.Asynchronous_Action();
+
+/**
+ * Initialization function for use by instance constructors.
+ * @param f
+ * @param duration
+ * @param finisher
+ * @param catcher
+ */
+Action.Delay_class.init = function( f, duration, finisher, catcher )
+{
+ Action.Asynchronous_Action.init.call( this, finisher, catcher );
+ this.trial = f;
+ this.duration = duration;
+};
+
+Action.Delay_class.prototype._go = function()
+{
+ this.timer_id = Action_Platform.set_timer( this._body.bind( this ), this.duration );
+};
+
+Action.Delay_class.prototype._body = function()
+{
+ if ( this.completed )
+ return;
+ try
+ {
+ if ( this.trial )
+ this._argv = this.trial();
+ }
+ catch ( e )
+ {
+ this.end_badly( e );
+ return;
+ }
+ this.end_well();
+};
+
+Action.Delay_class.prototype.terminate = function()
+{
+ Action_Platform.clear_timer( this.timer_id );
+};
+
+Action.Delay = function( f, duration, finisher, catcher )
+{
+ Action.Delay_class.init.call( this, f, duration, finisher, catcher );
+};
+Action.Delay.prototype = new Action.Delay_class();
+
+//-------------------------------------------------------
+// Join_class
+//-------------------------------------------------------
+/**
+ * Class constructor for objects that join on a single action, perhaps with modifications.
+ *
+ * The policy for this class is that completion of the joined action causes completion of this action. This is a simple
+ * join policy. Joining on more than action requires a more complicated action. This policy, however, is suitable not
+ * only for Join itself, but also for Join_Timeout. Join_Timeout also depends only upon a single action; its timer is
+ * internal and doesn't incur the overhead of an action for that simple case.
+ *
+ * @constructor
+ * @extends {Action.Asynchronous_Action}
+ */
+Action.Join_class = function()
+{
+};
+Action.Join_class.prototype = new Action.Asynchronous_Action();
+
+/**
+ * Initialization function for instance constructors.
+ *
+ * @this {Action.Join_class}
+ * @param action
+ * @param finisher
+ * @param catcher
+ */
+Action.Join_class.init = function( action, finisher, catcher )
+{
+ if ( !action )
+ throw new Error( "Action to be joined may not be null" );
+ Action.Asynchronous_Action.init.call( this, finisher, catcher );
+ this.joined_action = action;
+};
+
+/**
+ * The action body for a join is to do nothing when the joined action is not yet completed.
+ * @private
+ */
+Action.Join_class.prototype._go = function()
+{
+ this._argv = Array.prototype.slice.call( arguments );
+
+ if ( this.joined_action.completed )
+ {
+ if ( this.joined_action.completed_well )
+ {
+ this.end_well();
+ }
+ else
+ {
+ this.end_badly( this.joined_action.exception );
+ }
+ }
+ else
+ {
+ this.watch_id = this.joined_action.watch( this, null );
+ }
+};
+
+/**
+ * A good completion of the joined action yields a good completion for us.
+ */
+Action.Join_class.prototype.notice_good = function()
+{
+ this.end_well();
+};
+
+/**
+ * A bad completion of the joined action yields a bad completion for us.
+ */
+Action.Join_class.prototype.notice_bad = function( id, e )
+{
+ this.end_badly( e );
+};
+
+/**
+ * We treat object references as resources and null them out. This is a defense against creating or aggravating
+ * memory leaks.
+ */
+Action.Join_class.prototype.terminate = function()
+{
+ this.joined_action.withdraw( this.watch_id );
+ this.joined_action = null;
+};
+
+//-------------------------------------------------------
+// Join
+//-------------------------------------------------------
+/**
+ * Join with another action. The completion of the action joined allows the join action to complete.
+ *
+ * @constructor
+ * @param {Action.Asynchronous_Action} action
+ * @param [finisher]
+ * @param [catcher]
+ */
+Action.Join = function( action, finisher, catcher )
+{
+ Action.Join_class.init.call( this, action, finisher, catcher );
+};
+Action.Join.prototype = new Action.Join_class();
+
+//-------------------------------------------------------
+// Join_Timeout
+//-------------------------------------------------------
+/**
+ * Join with another action and set a timer that may preemptively complete.
+ *
+ * Note that the timer starts on go() regardless of the state of the joined action. If you invoke a Join_Timeout on an
+ * action which never itself is invoked, then the timeout always causes completion.
+ *
+ * It would be perfectly reasonable to have an action which started a timer when some other action began, but that would
+ * require a subclass of Asynchronous_Action that had a hook at the start of execution, rather than merely upon
+ * completion.
+ *
+ * @constructor
+ * @param {Action.Asynchronous_Action} action
+ * @param duration
+ * @param [finisher]
+ * @param [catcher]
+ */
+Action.Join_Timeout = function( action, duration, finisher, catcher )
+{
+ Action.Join_class.init.call( this, action, finisher, catcher );
+
+ /**
+ * The identifier of the platform timer. It's used for early termination of the timer, if needed.
+ * @type {*}
+ */
+ this._timer_id = null;
+
+ this.duration = duration;
+
+ /**
+ * Flag indicating that the action has timed out.
+ * @type {boolean}
+ * @private
+ */
+ this._timed_out = false;
+};
+Action.Join_Timeout.prototype = new Action.Join_class();
+
+/**
+ * Flag indicating that the action has timed out. This is a read-only version of a private property, but with the
+ * caveat that calling it is only valid in a completed state.
+ *
+ * @this {Action.Join_Timeout}
+ * @return {boolean}
+ */
+Object.defineProperty( Action.Join_Timeout.prototype, "timed_out", {
+ /**
+ * @this {Action.Join_Timeout}
+ */
+ get: function()
+ {
+ if ( this.completed )
+ {
+ return this._timed_out;
+ }
+ else
+ {
+ throw new Error( "Action is not yet completed." );
+ }
+ }
+} );
+
+Action.Join_Timeout.prototype._go = function()
+{
+ Action.Join_class.prototype._go.apply( this, arguments );
+ if ( !this.completed )
+ {
+ this._timer_id = Action_Platform.set_timer( this.ding.bind( this ), this.duration );
+ }
+};
+
+/**
+ * The timer just went off.
+ */
+Action.Join_Timeout.prototype.ding = function()
+{
+ this._timer_id = null;
+ /*
+ * If we've already completed, we don't change the completion state.
+ */
+ if ( this.completed )
+ return;
+ /*
+ * Since we haven't completed at this point, the timer has gone off before the action completed. Timeout is
+ * considered an exceptional completion.
+ */
+ this._timed_out = true;
+ this.end_badly( new Error( "Action timed out." ) );
+};
+
+/**
+ * Termination requires clearing any timer that may still be active.
+ *
+ * @override
+ */
+Action.Join_Timeout.prototype.terminate = function()
+{
+ /*
+ * Clear the timer so it doesn't ding. Clear the timer id because as an object reference it acts as a resource.
+ */
+ if ( this._timer_id )
+ {
+ Action_Platform.clear_timer( this._timer_id );
+ this._timer_id = null;
+ }
+
+ Action.Join_class.prototype.terminate.call( this );
+};
+
+//-------------------------------------------------------
+/**
+ * @constructor
+ * @implements {Action.JM_Attentive}
+ * @param {Array.Joinable} actions
+ */
+Action.Join_Conjunction = function( actions )
+{
+ /**
+ * The conjunction of actions is reliable only if all the actions are reliable.
+ */
+ this.reliable = true;
+ for ( var j = 0 ; j < actions.length ; ++j )
+ {
+ if ( !actions[ j ].reliable )
+ {
+ this.reliable = false;
+ break;
+ }
+ }
+};
+
+Action.Join_Conjunction.prototype.notice_good = function()
+{
+};
+
+Action.Join_Conjunction.prototype.notice_bad = function()
+{
+};
+
+/*
+ * The guard around the definition of 'exports' is present because in js-test-driver it's not in scope.
+ */
+if ( typeof exports === 'undefined' )
+ exports = {};
+exports.Action = Action;
« no previous file with comments | « chrome/skin/text-x-source.png ('k') | lib/action_platform.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld