Home Manual Reference Source Test

lib/functions.js

import Ticker from '@statetree/ticker';
import {executeInSyncOrAsync, executeEntries, disposeAndRemoveEntry,executeAsync} from './helpers';
import Entry from './entry';

function getTickerEntry(func, context, priority, errorCallback, funcsInst){
	const ticker = new Ticker(func, context, priority);
	function doneNotifier(){
		funcsInst.remainingEntries = funcsInst.remainingEntries - 1;
	};
	ticker.onDone(doneNotifier).onError(errorCallback);
	return new Entry(ticker.executeInCycle, ticker);
}

export default class Functions {

	constructor(connector = null) {
		this._entries = [];
		this._tickerEntries = []; // ticker entries
		this.remainingEntries = 0;
		this.connector = connector; // connector is responsible for sequencing next function set
		this._enableConnector = true;
	}
	/**
	 * Based on return value of predicate, this function decides whether to execute the method immediately or in frame cycle
	 *
	 * @param {function} apiFunc
	 * @param {function} callback Api func execution may be sync or Async, if its sync we cant return notifier as user can register doneCallback after API invocation
	 * @param {function} errorCallback
	 * @return {void}
	 */
	executeWhenIdle(apiFunc, callback, errorCallback){
		const predicate = ()=>{
			if(this.remainingEntries < 0){
				throw new Error("There can't be negative entries")
			}
			return this.remainingEntries === 0;
		};
		if(!errorCallback){
			errorCallback = (error)=>console.log(error);
		}
		executeInSyncOrAsync(predicate, apiFunc, callback, errorCallback);
	};

	/**
	 * Based on return value of predicate, this function decides whether to execute the method in immediate frame cycle or following cycle
	 *
	 * @param {function} apiFunc
	 * @return {object} notifier
	 */
	executeAsyncWhenIdle(apiFunc){
		const predicate = ()=>{
			if(this.remainingEntries < 0){
				throw new Error("There can't be negative entries")
			}
			return this.remainingEntries === 0;
		};
		return executeAsync(predicate, apiFunc)
	}

	/**
	 * execute all the entries
	 *
	 * @param {function} callback
	 * @return {void}
	 */
	trigger(callback = null){
		const that = this;
		const _trigger = ()=>{
			const {_entries, _tickerEntries} = that;
			const shouldTrigger = that.shouldExecuteFunctions();
			if(shouldTrigger){
				that.functionsWillExecute();
				(_entries.length > 0) && executeEntries(_entries);
				if (_tickerEntries.length > 0) {
					that.remainingEntries = executeEntries( _tickerEntries, true)
				};
			}
		};

		const entriesExecutionCompletedNotifier = ()=>{
			if(that.remainingEntries === 0){
				callback && callback()
				that.functionsDidExecute();
			} else {
				that.executeWhenIdle(entriesExecutionCompletedNotifier);
			}
		};

		this.executeWhenIdle( _trigger, entriesExecutionCompletedNotifier);
	};

	/**
	 * Add the function to entries
	 * if trigger is in progress, adds once its completed
	 *
	 * @param {function} func
	 * @param {object} context
	 * @param {boolean} executeInCycle
	 * @param {number} priority
	 * @param {function} callback
	 * @param {function} errorCallback
	 * @return {void}
	 */
	addFunction(func, context = null, executeInCycle = false, priority = 0, callback = null, errorCallback = null){
		const _addFunction = () => {
			let entry = new Entry(func, context, priority);
			if (executeInCycle){
				entry = getTickerEntry(func, context, priority, errorCallback, this);
				this._tickerEntries.push(entry);
			} else {
				entry = new Entry(func, context);
				this._entries.push(entry);
			}
			callback && callback(entry)
		};
		this.executeWhenIdle(_addFunction);
	};

	/**
	 * remove the function from entries
	 * if trigger is in progress, removes once its completed
	 *
	 * @param {function} func
	 * @param {object} context
	 * @param {function} callback
	 * @return {void}
	 */
	removeFunction(func, context = null, callback = null){
		const _removeFunction = ()=> {
			const {_tickerEntries, _entries} = this;
			disposeAndRemoveEntry(func,context,_entries);
			disposeAndRemoveEntry(func,context,_tickerEntries , true);
			callback && callback();
		}

		this.executeWhenIdle(_removeFunction);
	};

	/**
	 * Should be implemented by the class which inherits it,
	 * will be called before executing entries
	 */
	functionsWillExecute(){

	};

	/**
	 * Should be implemented by the class which inherits it,
	 * Should be a predicate function which decides whether to execute the entries.
	 * @return {boolean}
	 */
	shouldExecuteFunctions(){
		return true;
	};

	/**
	 * Should be extended by calling super.functionsDidExecute() by the class which inherits it,
	 * If connector is enabled , this will trigger the function that was referenced to connector
	 */
	functionsDidExecute(){
		this._enableConnector && this.connector && this.connector();
	};

	/**
	 * sets the connector function which is the binder for next sequence of actions
	 * if trigger is in progress, sets once its completed
	 *
	 * @param {function} connector
	 * @return {void}
	 */
	setConnector(connector){
		const _setConnector = () => {
			this.connector = connector;
		}
		this.executeWhenIdle(_setConnector);

	}

	/**
	 * removes the connector function which is the binder for next sequence of actions
	 * if trigger is in progress, removes once its completed
	 *
	 * @return {void}
	 */
	removeConnector(){
		const _removeConnector = () => {
			this.connector = null;
		};
		this.executeWhenIdle(_removeConnector);
	}

	/**
	 * enables the connector function which is the binder for next sequence of actions
	 * if trigger is in progress, enables once its completed
	 *
	 * @return {void}
	 */
	linkConnector(){
		const _linkConnector = () => {
			this._enableConnector = true;
		};
		this.executeWhenIdle(_linkConnector);
	}

	/**
	 * disables the connector function which is the binder for next sequence of actions
	 * if trigger is in progress, disables once its completed
	 *
	 * @return {void}
	 */
	unLinkConnector(){
		const _unLinkConnector = () => {
			this._enableConnector = false;
		};
		this.executeWhenIdle(_unLinkConnector);
	}
}