Skip to content
This repository has been archived by the owner on Nov 8, 2022. It is now read-only.

Commit

Permalink
feat(api): proper caching/in-flight handling of urls
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjamin Reed committed Sep 11, 2020
1 parent 92342fe commit 3780e38
Showing 1 changed file with 56 additions and 13 deletions.
69 changes: 56 additions & 13 deletions src/lib/api/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@ import { HttpResponse } from '@capacitor-community/http';
import { Team } from '../model/team';
import { Player } from '../model/player';

const DEFAULT_CACHE_KEEP = 10 * 60 * 1000; // 10 minutes

interface CacheEntry {
url: string,
response: Promise<HttpResponse>,
time: number,
}

@Injectable({
providedIn: 'root'
})
export class APIDatabase {
private cache = {} as { [key: string]: any };
private cache = {} as { [url: string]: CacheEntry };
private inFlight = {} as { [url: string]: Promise<HttpResponse> };
private root = 'https://cors-proxy.blaseball-reference.com/database';

constructor() {
Expand All @@ -23,18 +32,39 @@ export class APIDatabase {
});
}

private async get(url: string, force?: boolean) {
if (!force && this.cache[url]) {
return this.cache[url];
private async get(url: string, force?: boolean): Promise<HttpResponse> {
const now = Date.now();
const cacheEntry = this.cache[url];
const inFlight = this.inFlight[url];

if (!force) {
// if we're not forcing a new get, then check if there is an existing cached or in-flight request
if (inFlight) {
// if there's an in-flight one, just return it
console.debug(`APIDatabase.get(): already in-flight: ${url}`);
return inFlight;
} else if (cacheEntry) {
// if there's a cache entry...
if ((cacheEntry.time + DEFAULT_CACHE_KEEP) < now) {
// check if it's old; if it is, fall through to a new request
console.debug(`APIDatabase.get(): stale: ${url}`);
} else {
// otherwise, return the cached response
console.debug(`APIDatabase.get(): cached: ${url} (${new Date(cacheEntry.time).toISOString()})`);
return this.cache[url].response;
}
}
}


const { Http } = Plugins;

const ret = await Http.request({
console.debug(`APIDatabase.get(): uncached: ${url}`);
this.inFlight[url] = Http.request({
method: 'GET',
url: url,
}).catch((err:any) => {
console.error('request failed, trying again in 1s:', err);
console.error('APIDatabase.get(): request failed, trying again in 1s:', err);
return new Promise((resolve, reject) => {
setTimeout(async () => {
try {
Expand All @@ -49,18 +79,31 @@ export class APIDatabase {
}, 1000);
});
});
this.cache[url] = (ret as any).data;
// console.debug('data=', ret.data);
return (ret as any).data;

// delete from the in-flight list as soon as it finishes
this.inFlight[url].finally(() => {
delete this.inFlight[url];
});

return this.inFlight[url].then((ret: HttpResponse) => {
// if it succeeds, cache it
console.debug(`APIDatabase.get(): caching: ${url}`);
this.cache[url] = {
url: url,
response: Promise.resolve(ret),
time: now,
};
return ret;
});
}

public async teams(force?: boolean): Promise<Team[]> {
const url = `${this.root}/allTeams`;
console.debug(`APIDatabase.teams(): GET ${url}`);
// console.debug(`APIDatabase.teams(): GET ${url}`);
try {
const ret = await this.get(url, force);
if (ret) {
return ret.map((team:any) => new Team(team));
return ret.data.map((team:any) => new Team(team));
}
} catch (err) {
console.error('APIDatabase.teams(): failed to get list of teams', err);
Expand All @@ -77,11 +120,11 @@ export class APIDatabase {
return [] as Player[];
}
const url = `${this.root}/players?ids=${args.join(',')}`;
console.debug(`APIDatabase.players(): GET ${url}`);
// console.debug(`APIDatabase.players(): GET ${url}`);
try {
const ret = await this.get(url, force);
if (ret) {
return ret.map((player:any) => new Player(player));
return ret.data.map((player:any) => new Player(player));
}
} catch (err) {
console.error('APIDatabase.players(): failed to get players', err);
Expand Down

0 comments on commit 3780e38

Please sign in to comment.