Source: index.js

const { flatten } = require('lodash')


class AsyncLogic {
    /**
     * The root async logic environment which should be instantiated around any Async logical operators 
     * @constructor
     * @param {AsyncOr|AsyncAnd|AsyncNot} child - The root operator for an async logic operator
     */
    constructor(child){
        this.child = child;
        this.promises = [...this.child.getPromises()]
    }
    
    /**
     * The method to call to execute the comparison and return the appropriate reference value.
     * @async
     */
    async compute() {
        await Promise.all(this.promises)
        return await this.child.compute()
    }
}

class AsyncLogicalOperator {
    /**
     * The superclass for all other async logic operators. Cannot be used directly.
     * @constructor
     * @package
     * @param {function|async-function|AsyncOr|AsyncAnd|AsyncNot} children - Operands can either be functions which return a value to be evaluated or a nested logic operator
     */
    constructor( ...children ){
        // Make sure children which are not aysnc logic ops are called
        this.children = [ ...children ].map( (child) => {
            if ( child instanceof AsyncLogicalOperator ) return child
            else return child()
        })

        this.promises = flatten(
            this.children.map( (child) => {
                if ( child instanceof AsyncLogicalOperator ) return child.getPromises()
                else return child
            })
        )
    }

    /**
     * Utility function which returns the list of promises for the operators children
     * @package
     */
    getPromises() {
        return this.promises
    }

    /**
     * Utility function which computes the logical result of the operators children
     * @package
     * @async
     */
    async compute() {
        return this.children
            .map( async (child) => {
                if ( child instanceof AsyncLogicalOperator ) return await child.compute()
                else return await child
            })
            .reduce(this.operand)
    }
}

class AsyncOr extends AsyncLogicalOperator {
    /**
     * The AsyncOr class takes more than one input and asynchronously computes the chained logical or
     * @constructor
     * @public
     * @override
     * @extends AsyncLogicalOperator
     * @param {function|async-function|AsyncOr|AsyncAnd|AsyncNot} children - Operands can either be functions which return a value to be evaluated or a nested logic operator
     */
    constructor( ...children ){
        super(...children)
    }

    /**
     * Utility function which defines the behaviour of the operand
     * @async
     * @public
     */
    async operand ( accumulator, current ){
        return await accumulator || await current
    }
}

class AsyncAnd extends AsyncLogicalOperator {
    /**
     * The AsyncAnd class takes more than one input and asynchronously computes the chained logical and
     * @constructor
     * @public
     * @override
     * @extends AsyncLogicalOperator
     * @param {function|async-function|AsyncOr|AsyncAnd|AsyncNot} children - Operands can either be functions which return a value to be evaluated or a nested logic operator
     */
    constructor( ...children ){
        super(...children)
    }

    /**
     * Utility function which defines the behaviour of the operand
     * @async
     * @public
     */
    async operand ( accumulator, current ){
        return await accumulator && await current
    }
}

class AsyncNot extends AsyncLogicalOperator {
    /**
     * The AsyncNot class takes one input and asynchronously computes its logical inverse
     * @constructor
     * @public
     * @override
     * @extends AsyncLogicalOperator
     * @param {function|async-function|AsyncOr|AsyncAnd|AsyncNot} children - Operand must be one either a function which return a value to be evaluated or a nested logic operator
     */
    constructor( ...children ){
        super(...children)
        // Make sure children which are not aysnc logic ops are called
        this.children = [ ...children ].map( (child) => {
            if ( child instanceof AsyncLogicalOperator ){ 
                return child
            }
            else{ 
                return child()
            }
        })

        if (this.children.length !== 1) throw new Error('AsyncNot can only support one child')

        this.promises = flatten(
            this.children.map( (child) => {
                if ( child instanceof AsyncLogicalOperator ) return child.getPromises()
                else return child
            })
        )
    }
    /**
     * Utility function which defines the behaviour of the operand
     * @async
     * @package
     */
    async compute() {
        const child =  this.children[0]
        if ( child instanceof AsyncLogicalOperator ) return !(await child.compute())
        else return !(await child)
    }
}

module.exports = {
    AsyncLogic,
    AsyncOr,
    AsyncAnd,
    AsyncNot
}