Skip to content

Instantly share code, notes, and snippets.

@christophermark
Created October 24, 2018 22:43
Show Gist options
  • Save christophermark/dbca0c38a2660b0e4c20388e0f373538 to your computer and use it in GitHub Desktop.
Save christophermark/dbca0c38a2660b0e4c20388e0f373538 to your computer and use it in GitHub Desktop.
Selector Debugging
'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