bowheart/trikl

View on GitHub
src/trikl.js

Summary

Maintainability
A
0 mins
Test Coverage
'use strict'

module.exports = createFactory({})

const slice = Array.prototype.slice


// Factory creation and implementation functions:

function triklTemplate(drop) {
    let trikl = new Trikl(this)
    if (drop) trikl.drop(...arguments)
    return trikl
}

function createFactory(context) {
    let trikl = triklTemplate.bind(context)
    
    setFlagGetter(trikl, context, 'pure')
    setFlagGetter(trikl, context, 'halt')
    setFlagGetter(trikl, context, 'hard')
    
    return trikl
}

function setFlagGetter(trikl, context, flagName) {
    Object.defineProperty(trikl, flagName, {
        get() {
            let newContext = Object.assign({}, context) // shallowly clone the old context
            newContext[flagName] = true
            return createFactory(newContext)
        }
    })
}


// Trikl methods and definition:

function bindDrip(drip) {
    drip.bond = bond.bind(drip)
    drip.skip = registerSkip.bind(this)
    drip.stop = stopDrip.bind(this)
}

function bond() {
    return this.bind(this, ...arguments)
}

function drip() {
    let nextDrop = this.drops.shift()
    if (!nextDrop) return this.drip.stop(...arguments)
    
    this.dropArgs = [...arguments]
    
    try {
        this.isPure
            ? nextDrop(...this.dropArgs)
            : nextDrop(getDrip.call(this), ...this.dropArgs)
    } catch (ex) {
        stopDripWithError.call(this, ex)
    }
    return this // for chaining
}

function getDrip() {
    if (!this.isHard) return this.drip
    
    let newDrip = hardDrip.bind(this, {called: false})
    bindDrip.call(this, newDrip)
    return newDrip
}

function hardDrip(info) {
    if (info.called) return // this drip has already been called; cancel
    
    info.called = true
    let args = slice.call(arguments, 1) // trim off the info object
    return this.drip(...args)
}

function registerSkip(num = 1) {
    num = Math.max(parseInt(num) || 1, 1) // can't skip less than 1 item
    return skip.bind(this, num)
}

function skip(num) {
    let args = slice.call(arguments, 1) // trim the num off the arguments
    this.drops.splice(0, num)
    this.drip(...args)
    return this // for chaining
}

function stopDrip(val) {
    if (val instanceof Error) return stopDripWithError.call(this, ...arguments)
    this.drops = []
    this.resolve(...arguments)
    return this // for chaining
}

function stopDripWithError() {
    this.drops = []
    this.reject(...arguments)
    return this // for chaining
}

class Trikl {
    constructor({pure, halt, hard}) {
        this.isPure = pure
        this.isHard = hard
        this.promise = this.lastPromise = new Promise((resolve, reject) => {
            this.resolve = this.resolve.bind(this, resolve)
            this.reject = this.reject.bind(this, reject)
        })
        this.drops = []
        this.drip = drip.bind(this)
        bindDrip.call(this, this.drip)
        
        if (!halt) setTimeout(this.drip) // allow the drops to be added in this turn of the event loop
    }
    
    catch() {
        this.lastPromise = this.lastPromise.catch(...arguments)
        return this // for chaining
    }

    drop() {
        let args = slice.call(arguments)
        while (args.length) {
            let drop = args.shift()
            if (typeof drop !== 'function') {
                throw new TypeError('Trikl Error - trickle.drop() - Drop must be a function. Received "' + typeof drop + '".')
            }
            this.drops.push(drop)
        }
        return this // for chaining
    }
    
    resolve(resolve) {
        resolve(...slice.call(arguments, 1))
        return this // for chaining
    }
    
    reject(reject) {
        reject(...slice.call(arguments, 1))
        return this // for chaining
    }
    
    then() {
        this.lastPromise = this.lastPromise.then(...arguments)
        return this // for chaining
    }
}