futoin-asyncsteps
Version:
Mimic traditional threads in single threaded event loop
369 lines (304 loc) • 8.22 kB
JavaScript
"use strict";
/**
* @file Protector against AsyncSteps concept violation in use
* @author Andrey Galkin <andrey@futoin.org>
*
*
* Copyright 2014-2017 FutoIn Project (https://futoin.org)
* Copyright 2014-2017 Andrey Galkin <andrey@futoin.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const ParallelStep = require( './ParallelStep' );
const {
InternalError,
Timeout,
LoopBreak,
LoopCont,
} = require( '../Errors' );
const {
isProduction,
checkFunc,
checkOnError,
noop,
loop,
repeat,
forEach,
LOOP_TERM_LABEL,
as_await,
} = require( './common' );
const sanityCheck = isProduction ? noop : ( asp ) => {
const root = asp._root;
if ( root ) {
const stack = root._stack;
if ( stack ) {
if ( stack[stack.length - 1] === asp ) {
return;
}
root.error( InternalError, "Invalid call (sanity check)" );
}
}
throw new Error( `InternalError: Unexpected call, object is out of service` );
};
const sanityCheckAdd = isProduction ? noop : ( asp, func, onerror ) => {
sanityCheck( asp );
checkFunc( asp, func );
checkOnError( asp, onerror );
};
const on_timeout = ( asi ) => {
asi._limit_event = null;
const { state } = asi;
state.error_info = undefined;
state.last_exception = new Error( Timeout );
asi._root._handle_error( Timeout );
};
/* globals AsyncSteps */
/* globals CancelFunc */
/**
* AsyncStepProtector
* @private
* @class
* @param {AsyncSteps} [root] main object
*/
class AsyncStepProtector {
constructor( root, on_error, call_args ) {
this._root = root;
this.state = root.state;
this._queue = null;
this._call_args = call_args;
this._on_error = on_error;
this._on_cancel = null;
this._limit_event = null;
}
/**
* @private
* @override
*/
add( func, onerror ) {
sanityCheckAdd( this, func, onerror );
const s = [ func, onerror ];
const q = this._queue;
if ( q ) {
q.push( s );
} else {
this._queue = [ s ];
}
return this;
}
/**
* @private
* @override
*/
parallel( onerror ) {
var p = new ParallelStep( this._root, this );
this.add(
( as ) => {
p.executeParallel( as );
},
onerror,
);
return p;
}
/**
* Successfully complete current step execution, optionally passing result variables to the next step.
* @param {...any} [args] - unlimited number of result variables with no type constraint
* @alias AsyncSteps#success
*/
success( ...args ) {
sanityCheck( this );
if ( this._queue !== null ) {
this.error( InternalError, "Invalid success() call" );
}
this._root._handle_success( args );
}
/**
* @private
* @override
*/
error( name, error_info ) {
sanityCheck( this );
this._root.error( name, error_info );
}
/**
* Set timeout for external event completion with async *as.success()* or *as.error()* call.
* If step is not finished until timeout is reached then Timeout error is raised.
* Can be used only within **ExecFunc** body.
* @param {number} timeout_ms - Timeout in ms
* @returns {AsyncSteps} self
* @alias AsyncSteps#setTimeout
*/
setTimeout( timeout_ms ) {
sanityCheck( this );
const async_tool = this._root._async_tool;
if ( this._limit_event !== null ) {
async_tool.cancelCall( this._limit_event );
}
this._limit_event = async_tool.callLater(
( ) => {
on_timeout( this );
},
timeout_ms,
);
return this;
}
/**
* Set cancellation handler to properly handle timeouts and external cancellation.
* Can be used only within **ExecFunc** body.
* @param {CancelFunc} oncancel - cleanup/cancel logic of external processing
* @returns {AsyncSteps} self
* @alias AsyncSteps#setCancel
*/
setCancel( oncancel ) {
this._on_cancel = oncancel;
return this;
}
/**
* Mark currently executing step as waiting for external event.
* Can be used only within **ExecFunc** body.
* @returns {AsyncSteps} self
* @alias AsyncSteps#waitExternal
*/
waitExternal( ) {
this._on_cancel = noop;
return this;
}
/**
* @private
* @override
*/
copyFrom( other ) {
sanityCheck( this );
if ( other._queue.length ) {
let q = this._queue;
if ( q === null ) {
q = [];
this._queue = q;
}
q.push.apply( q, other._queue );
}
const os = other.state;
const s = this.state;
for ( let k in os ) {
if ( s[k] === undefined ) {
s[k] = os[k];
}
}
return this;
}
/**
* @private
* @override
*/
loop( func, label ) {
sanityCheckAdd( this, func );
loop( this, this._root, func, label );
return this;
}
/**
* @private
* @override
*/
repeat( count, func, label ) {
sanityCheckAdd( this, func );
repeat( this, this._root, count, func, label );
return this;
}
/**
* @private
* @override
*/
forEach( map_or_list, func, label ) {
sanityCheckAdd( this, func );
forEach( this, this._root, map_or_list, func, label );
return this;
}
/**
* Break execution of current loop, throws exception
* @param {string=} label - Optional. unwind loops, until *label* named loop is exited
* @alias AsyncSteps#break
*/
break( label ) {
sanityCheck( this );
this.state[LOOP_TERM_LABEL] = label;
this._root.error( LoopBreak );
}
/**
* Continue loop execution from the next iteration, throws exception
* @param {string=} label - Optional. unwind loops, until *label* named loop is found
* @alias AsyncSteps#continue
*/
continue( label ) {
sanityCheck( this );
this.state[LOOP_TERM_LABEL] = label;
this._root.error( LoopCont );
}
/**
* @private
* @override
*/
successStep( ...args ) {
sanityCheck( this );
const queue = this._queue;
if ( queue && queue.length ) {
queue.push( [ () => {
this._root._handle_success( args );
}, undefined ] );
} else {
this._root._next_args = args;
}
return this;
}
/**
* @private
* @override
*/
await( promise, onerror ) {
sanityCheck( this );
as_await( this, this._root, promise, onerror );
return this;
}
/**
* @private
* @override
*/
sync( object, func, onerror ) {
sanityCheckAdd( this, func, onerror );
object.sync( this, func, onerror );
return this;
}
/**
* @private
* @override
*/
newInstance() {
return this._root.newInstance();
}
/**
* @private
* @override
*/
isAsyncSteps() {
return true;
}
/**
* @private
*/
_cleanup() {
this._root = null;
//this._queue = null;
//this._on_error = null;
//this._on_cancel = null;
this.state = null;
}
}
module.exports = AsyncStepProtector;