11import type { EqualityFn } from './types'
22
3- export const defaultEqualityCheck : EqualityFn = ( a , b ) : boolean => {
4- return a === b
3+ // Cache implementation based on Erik Rasmussen's `lru-memoize`:
4+ // https://github.com/erikras/lru-memoize
5+
6+ interface Entry {
7+ key : any
8+ value : any
9+ }
10+
11+ interface Cache {
12+ get ( key : any ) : Entry | undefined
13+ put ( key : any , value : any ) : void
514}
615
7- function areArgumentsShallowlyEqual (
8- equalityCheck : EqualityFn ,
9- prev : unknown [ ] | IArguments | null ,
10- next : unknown [ ] | IArguments | null
11- ) : boolean {
12- if ( prev === null || next === null || prev . length !== next . length ) {
13- return false
16+ function createSingletonCache ( equals : EqualityFn ) : Cache {
17+ let entry : Entry
18+ return {
19+ get ( key : any ) {
20+ if ( entry && equals ( entry . key , key ) ) {
21+ return entry . value
22+ }
23+ } ,
24+
25+ put ( key : any , value : any ) {
26+ entry = { key, value }
27+ }
1428 }
29+ }
1530
16- // Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
17- const length = prev . length
18- for ( let i = 0 ; i < length ; i ++ ) {
19- if ( ! equalityCheck ( prev [ i ] , next [ i ] ) ) {
20- return false
31+ function createLruCache ( maxSize : number , equals : EqualityFn ) : Cache {
32+ const entries : Entry [ ] = [ ]
33+
34+ function get ( key : any ) {
35+ const cacheIndex = entries . findIndex ( entry => equals ( key , entry . key ) )
36+
37+ // We found a cached entry
38+ if ( cacheIndex > - 1 ) {
39+ const entry = entries [ cacheIndex ]
40+
41+ // Cached entry not at top of cache, move it to the top
42+ if ( cacheIndex > 0 ) {
43+ entries . splice ( cacheIndex , 1 )
44+ entries . unshift ( entry )
45+ }
46+
47+ return entry . value
48+ }
49+
50+ // No entry found in cache, return null
51+ return undefined
52+ }
53+
54+ function put ( key : any , value : any ) {
55+ if ( ! get ( key ) ) {
56+ // TODO Is unshift slow?
57+ entries . unshift ( { key, value } )
58+ if ( entries . length > maxSize ) {
59+ entries . pop ( )
60+ }
2161 }
2262 }
2363
24- return true
64+ return { get, put }
65+ }
66+
67+ export const defaultEqualityCheck : EqualityFn = ( a , b ) : boolean => {
68+ return a === b
69+ }
70+
71+ function createCacheKeyComparator ( equalityCheck : EqualityFn ) {
72+ return function areArgumentsShallowlyEqual (
73+ prev : unknown [ ] | IArguments | null ,
74+ next : unknown [ ] | IArguments | null
75+ ) : boolean {
76+ if ( prev === null || next === null || prev . length !== next . length ) {
77+ return false
78+ }
79+
80+ // Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
81+ const length = prev . length
82+ for ( let i = 0 ; i < length ; i ++ ) {
83+ if ( ! equalityCheck ( prev [ i ] , next [ i ] ) ) {
84+ return false
85+ }
86+ }
87+
88+ return true
89+ }
2590}
2691
2792export interface DefaultMemoizeOptions {
2893 equalityCheck ?: EqualityFn
94+ resultEqualityCheck ?: EqualityFn
95+ maxSize ?: number
2996}
3097
3198// defaultMemoize now supports a configurable cache size and comparison of the result value.
@@ -34,25 +101,35 @@ export function defaultMemoize<F extends (...args: any[]) => any>(
34101 func : F ,
35102 equalityCheckOrOptions ?: EqualityFn | DefaultMemoizeOptions
36103) : F {
37- let lastArgs : any = null
38- let lastResult : any = null
39-
40104 const providedOptions =
41105 typeof equalityCheckOrOptions === 'object'
42106 ? equalityCheckOrOptions
43107 : { equalityCheck : equalityCheckOrOptions }
44108
45- const { equalityCheck = defaultEqualityCheck } = providedOptions
109+ const {
110+ equalityCheck = defaultEqualityCheck ,
111+ maxSize = 1 ,
112+ resultEqualityCheck
113+ } = providedOptions
114+
115+ const comparator = createCacheKeyComparator ( equalityCheck )
116+ let resultComparator = resultEqualityCheck
117+ ? createCacheKeyComparator ( resultEqualityCheck )
118+ : undefined
119+
120+ const cache =
121+ maxSize === 1
122+ ? createSingletonCache ( comparator )
123+ : createLruCache ( maxSize , comparator )
46124
47125 // we reference arguments instead of spreading them for performance reasons
48126 return function ( ) {
49- if ( ! areArgumentsShallowlyEqual ( equalityCheck , lastArgs , arguments ) ) {
50- // apply arguments instead of spreading for performance.
127+ let value = cache . get ( arguments )
128+ if ( value === undefined ) {
51129 // @ts -ignore
52- lastResult = func . apply ( null , arguments )
130+ value = func . apply ( null , arguments )
131+ cache . put ( arguments , value )
53132 }
54-
55- lastArgs = arguments
56- return lastResult
133+ return value
57134 } as F
58135}
0 commit comments