Skip to content

Commit d8696fa

Browse files
acdlitejetoneza
authored andcommitted
[react-cache] Remove cache as argument to read (facebook#13865)
* [react-cache] Remove `cache` as argument to `read` Updated is API is `Resource.read(key)` instead of `Resource.read(cache, key)`. The cache is read from context using `readContext`. This also removes cache invalidation entirely (other than the default LRU mechanism), as well as the ability to have multiple caches. We'll add it back once `Context.write` lands and we can implement it the right way. Since there's now only a single cache (the global one), we don't actually need to use context yet, but I've added a dummy context anyway so the user gets an error if they attempt to read outside the render phase. * nits * Add test for thenables that resolve multiple times
1 parent cb6fe03 commit d8696fa

11 files changed

+732
-692
lines changed

packages/react-cache/src/LRU.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* Copyright (c) 2014-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import {unstable_scheduleCallback as scheduleCallback} from 'scheduler';
11+
12+
type Entry<T> = {|
13+
value: T,
14+
onDelete: () => mixed,
15+
previous: Entry<T>,
16+
next: Entry<T>,
17+
|};
18+
19+
export function createLRU<T>(limit: number) {
20+
let LIMIT = limit;
21+
22+
// Circular, doubly-linked list
23+
let first: Entry<T> | null = null;
24+
let size: number = 0;
25+
26+
let cleanUpIsScheduled: boolean = false;
27+
28+
function scheduleCleanUp() {
29+
if (cleanUpIsScheduled === false && size > LIMIT) {
30+
// The cache size exceeds the limit. Schedule a callback to delete the
31+
// least recently used entries.
32+
cleanUpIsScheduled = true;
33+
scheduleCallback(cleanUp);
34+
}
35+
}
36+
37+
function cleanUp() {
38+
cleanUpIsScheduled = false;
39+
deleteLeastRecentlyUsedEntries(LIMIT);
40+
}
41+
42+
function deleteLeastRecentlyUsedEntries(targetSize: number) {
43+
// Delete entries from the cache, starting from the end of the list.
44+
if (first !== null) {
45+
const resolvedFirst: Entry<T> = (first: any);
46+
let last = resolvedFirst.previous;
47+
while (size > targetSize && last !== null) {
48+
const onDelete = last.onDelete;
49+
const previous = last.previous;
50+
last.onDelete = (null: any);
51+
52+
// Remove from the list
53+
last.previous = last.next = (null: any);
54+
if (last === first) {
55+
// Reached the head of the list.
56+
first = last = null;
57+
} else {
58+
(first: any).previous = previous;
59+
previous.next = (first: any);
60+
last = previous;
61+
}
62+
63+
size -= 1;
64+
65+
// Call the destroy method after removing the entry from the list. If it
66+
// throws, the rest of cache will not be deleted, but it will be in a
67+
// valid state.
68+
onDelete();
69+
}
70+
}
71+
}
72+
73+
function add(value: T, onDelete: () => mixed): Entry<T> {
74+
const entry = {
75+
value,
76+
onDelete,
77+
next: (null: any),
78+
previous: (null: any),
79+
};
80+
if (first === null) {
81+
entry.previous = entry.next = entry;
82+
first = entry;
83+
} else {
84+
// Append to head
85+
const last = first.previous;
86+
last.next = entry;
87+
entry.previous = last;
88+
89+
first.previous = entry;
90+
entry.next = first;
91+
92+
first = entry;
93+
}
94+
size += 1;
95+
return entry;
96+
}
97+
98+
function update(entry: Entry<T>, newValue: T): void {
99+
entry.value = newValue;
100+
}
101+
102+
function access(entry: Entry<T>): T {
103+
const next = entry.next;
104+
if (next !== null) {
105+
// Entry already cached
106+
const resolvedFirst: Entry<T> = (first: any);
107+
if (first !== entry) {
108+
// Remove from current position
109+
const previous = entry.previous;
110+
previous.next = next;
111+
next.previous = previous;
112+
113+
// Append to head
114+
const last = resolvedFirst.previous;
115+
last.next = entry;
116+
entry.previous = last;
117+
118+
resolvedFirst.previous = entry;
119+
entry.next = resolvedFirst;
120+
121+
first = entry;
122+
}
123+
} else {
124+
// Cannot access a deleted entry
125+
// TODO: Error? Warning?
126+
}
127+
scheduleCleanUp();
128+
return entry.value;
129+
}
130+
131+
function setLimit(newLimit: number) {
132+
LIMIT = newLimit;
133+
scheduleCleanUp();
134+
}
135+
136+
return {
137+
add,
138+
update,
139+
access,
140+
setLimit,
141+
};
142+
}

0 commit comments

Comments
 (0)