Created
October 24, 2018 22:43
-
-
Save christophermark/dbca0c38a2660b0e4c20388e0f373538 to your computer and use it in GitHub Desktop.
Selector Debugging
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
function getDependencies(funcs) { | |
const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs | |
if (!dependencies.every(dep => typeof dep === 'function')) { | |
const dependencyTypes = dependencies.map( | |
dep => typeof dep | |
).join(', ') | |
throw new Error( | |
'Selector creators expect all input-selectors to be functions, ' + | |
`instead received the following types: [${dependencyTypes}]` | |
) | |
} | |
return dependencies | |
} | |
function createSelectorCreator(memoize = defaultMemoize, ...memoizeOptions) { | |
return (...funcs) => { | |
let recomputations = 0 | |
const resultFunc = funcs.pop() | |
const dependencies = getDependencies(funcs) | |
const memoizedResultFunc = debugMemoize( | |
function () { | |
recomputations++ | |
// apply arguments instead of spreading for performance. | |
return resultFunc.apply(null, arguments) | |
}, | |
...memoizeOptions | |
) | |
// If a selector is called with the exact same arguments we don't need to traverse our dependencies again. | |
const selector = memoize(function () { | |
const params = [] | |
const length = dependencies.length | |
for (let i = 0; i < length; i++) { | |
// apply arguments instead of spreading and mutate a local list of params for performance. | |
params.push(dependencies[i].apply(null, arguments)) | |
} | |
// apply arguments instead of spreading for performance. | |
return memoizedResultFunc.apply(null, params) | |
}) | |
selector.resultFunc = resultFunc | |
selector.dependencies = dependencies | |
selector.recomputations = () => recomputations | |
selector.resetRecomputations = () => recomputations = 0 | |
return selector | |
} | |
} | |
let diff = (updated, old) => Object.keys(updated).reduce((diff, key) => { | |
if (old[key] === updated[key]) return diff; | |
return { | |
...diff, | |
[key]: updated[key] | |
}; | |
}, {}); | |
function debugAreArgumentsShallowlyEqual(equalityCheck, prev, next) { | |
if (prev === null || next === null || prev.length !== next.length) { | |
return false | |
} | |
// Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible. | |
const length = prev.length | |
for (let i = 0; i < length; i++) { | |
if (!equalityCheck(prev[i], next[i])) { | |
console.log('---') | |
console.log('Argument index', i) | |
console.log('inequal prev', prev[i]) | |
console.log('inequal next', next[i]) | |
console.log('diff', diff(next[i], prev[i])) | |
console.log('---') | |
return false | |
} | |
} | |
return true | |
} | |
function areArgumentsShallowlyEqual(equalityCheck, prev, next) { | |
if (prev === null || next === null || prev.length !== next.length) { | |
return false | |
} | |
// Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible. | |
const length = prev.length | |
for (let i = 0; i < length; i++) { | |
if (!equalityCheck(prev[i], next[i])) { | |
return false | |
} | |
} | |
return true | |
} | |
export function defaultMemoize(func) { | |
let equalityCheck = (a, b) => a === b | |
let lastArgs = null | |
let lastResult = null | |
// we reference arguments instead of spreading them for performance reasons | |
return function () { | |
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) { | |
// apply arguments instead of spreading for performance. | |
lastResult = func.apply(null, arguments) | |
} | |
lastArgs = arguments | |
return lastResult | |
} | |
} | |
function debugMemoize(func) { | |
let equalityCheck = (a, b) => a === b | |
let lastArgs = null | |
let lastResult = null | |
// we reference arguments instead of spreading them for performance reasons | |
return function () { | |
if (!debugAreArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) { | |
// apply arguments instead of spreading for performance. | |
lastResult = func.apply(null, arguments) | |
} | |
lastArgs = arguments | |
return lastResult | |
} | |
} | |
export const createDebugSelector = createSelectorCreator(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment