All files decorator.ts

98.04% Statements 50/51
95.83% Branches 23/24
100% Functions 6/6
97.96% Lines 48/49

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93  1x     1x   1x 5x 5x       5x 5x   5x 5x     5x 3x 3x 2x   2x 2x             5x 1x 3x 1x 1x       5x     42x 17x 17x   17x 11x 11x     11x 3x 3x 3x 3x 8x 4x 4x 4x   4x     11x     17x   6x 3x 3x 3x   3x     6x     17x 5x           17x        
import { CacheKeyFn } from './fn'
import { exactEqual } from './utils'
// import { exactEqual } from './utils'
 
let memoizeId = 0
 
export function memoize(cacheKeyFn?: CacheKeyFn<any>) {
    return function(target: any, key: string, descriptor: any): any {
        const memoizePrefix = `memoize:${memoizeId++}`
 
        let fnKey: string
        let fn: any
        let hasRun = false
        let newParams: any[] = []
 
        const cachedLastValue = new Map<string, ReturnType<any>>()
        const cachedLastParams = new Map<string, Parameters<any>>()
 
        // memoize on methods
        if (typeof descriptor.value === 'function') {
            fnKey = 'value'
            fn = descriptor.value
        } else Eif (typeof descriptor.get === 'function') {
            // memoize on getters
            fnKey = 'get'
            fn = descriptor.get
        } else {
            throw new Error(
                'decorated memoize on un supported property or method'
            )
        }
 
        if (typeof descriptor.set === 'function') {
            const rawSetter = descriptor.set
            descriptor.set = function(...args: any[]) {
                hasRun = false // intercept setter to mark getter as refreshed
                rawSetter!.apply(this, args)
            }
        }
 
        const memoizeKey = `${memoizePrefix}:${key}`
 
        // override original method or getter
        descriptor[fnKey] = function(...args: any[]) {
            const _self = this // the instance
            newParams = args;
 
            const methodUpdater = function() {
                const key = cacheKeyFn?.(...args) || 'only'
                const lastParams = cachedLastParams.get(key) || []
 
                let lastValue
                if (!hasRun) {
                    hasRun = true
                    lastValue = fn.apply(_self, newParams)
                    cachedLastValue.set(key, lastValue!)
                    cachedLastParams.set(key, newParams)
                } else if (!exactEqual(newParams, lastParams)) {
                    lastValue = fn.apply(_self, newParams)
                    cachedLastValue.set(key, lastValue!)
                    cachedLastParams.set(key, newParams)
                } else {
                    lastValue = cachedLastValue.get(key)!
                }
 
                return lastValue
            }
 
            const getterUpdater = function() {
                let lastValue
                if (!hasRun) {
                    lastValue = fn.apply(_self)
                    hasRun = true
                    cachedLastValue.set(key, lastValue!)
                } else {
                    lastValue = cachedLastValue.get(key)!
                }
 
                return lastValue
            }
 
            if (!this.hasOwnProperty(memoizeKey)) {
                Object.defineProperty(this, memoizeKey, {
                    configurable: false,
                    enumerable: false,
                    get: fnKey === 'value' ? methodUpdater : getterUpdater
                })
            }
            return this[memoizeKey]
        }
    }
}