Created
November 24, 2019 04:26
-
-
Save jamesknelson/f96693d47818fafebe5b52bfd5c05f83 to your computer and use it in GitHub Desktop.
A model class for storing data fetched with React Suspense
This file contains hidden or 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
const PurgeDelay = 1000 | |
class Model { | |
fetcher: (id: string) => Promise<any> | |
cache = {} | |
callbacks = {} | |
holds = {} | |
purgeTimeouts = {} | |
constructor(fetcher) { | |
this.fetcher = fetcher | |
} | |
getCurrentValue(id) { | |
return this.cache[id] || { id, status: 'pending' } | |
} | |
subscribe(id, callback) { | |
const release = this.hold(id) | |
const cached = this.cache[id] | |
let callbacks = this.callbacks[id] | |
if (!callbacks) { | |
this.callbacks[id] = callbacks = [] | |
} | |
if (callbacks.length === 0 && !cached) { | |
this.refresh(id) | |
} | |
callbacks.push(callback) | |
let isUnsubscribed = false | |
return () => { | |
if (!isUnsubscribed) { | |
release() | |
const index = callbacks.indexOf(callback) | |
callbacks.splice(index, 1) | |
if (callbacks.length === 0) { | |
delete this.callbacks[id] | |
} | |
isUnsubscribed = true | |
} | |
} | |
} | |
hold(id) { | |
const existingHolds = this.holds[id] || 0 | |
this.holds[id] = existingHolds + 1 | |
if (existingHolds === 0) { | |
this.cancelScheduledPurge(id) | |
} | |
let isUnsubscribed = false | |
return () => { | |
if (!isUnsubscribed) { | |
const holdsAfterRelease = --this.holds[id] | |
if (holdsAfterRelease === 0) { | |
delete this.holds[id] | |
this.schedulePurge(id) | |
} | |
isUnsubscribed = true | |
} | |
} | |
} | |
async refresh(id) { | |
const release = this.hold(id) | |
this.update(id, 'pending') | |
const cached = this.cache[id] | |
try { | |
const data = await this.fetcher(id) | |
if (cached === this.cache[id]) { | |
this.update(id, 'mirrored', data === undefined ? null : data) | |
} | |
} | |
catch (error) { | |
if (cached === this.cache[id]) { | |
this.update(id, 'error') | |
} | |
} | |
finally { | |
release() | |
} | |
} | |
update(id, status, data?) { | |
const currentValue = this.getCurrentValue(id) | |
// We don't want to notify subscribers unless something has changed | |
if (currentValue.status === status && currentValue.data === data) { | |
return | |
} | |
const resource = { id, status, data: data === undefined ? currentValue.data : data } | |
this.cache[id] = resource | |
const callbacks = this.callbacks[id] | |
if (callbacks) { | |
for (let callback of callbacks.slice(0)) { | |
callback(callback) | |
} | |
} else { | |
this.schedulePurge(id) | |
} | |
} | |
private cancelScheduledPurge(id) { | |
const purgeTimeout = this.purgeTimeouts[id] | |
if (purgeTimeout) { | |
clearTimeout(purgeTimeout) | |
delete this.purgeTimeouts[id] | |
} | |
} | |
private schedulePurge(id) { | |
this.cancelScheduledPurge(id) | |
this.purgeTimeouts[id] = setTimeout(() => { | |
delete this.purgeTimeouts[id] | |
if (!this.callbacks[id]) { | |
delete this.cache[id] | |
} | |
}, PurgeDelay) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment