Skip to content

Commit

Permalink
feat: add base listing implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
thislooksfun committed Mar 12, 2021
1 parent ffbe5b1 commit 90b8f2a
Showing 1 changed file with 113 additions and 0 deletions.
113 changes: 113 additions & 0 deletions src/listings/listing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import type { Data } from "../helper/types";
import type Client from "../client";
import { group } from "../helper/util";

export interface Context {
client: Client;
post: string;
}

export interface RedditMore {
count: number;
name: string;
id: string;
parent_id: string;
depth: number;
children: string[];
}

export abstract class More<T> {
protected data: RedditMore;

constructor(data: RedditMore) {
this.data = data;
}

abstract fetch(ctx: Context): Promise<Listing<T>>;

protected async more(ctx: Context): Promise<Data[]> {
// api/morechildren has a max of 20 ids at a time, so we have to batch it.
const children = this.data.children;
const fetches = group(children, 20).map(c => {
const query = { children: c.join(","), link_id: `t3_${ctx.post}` };
return ctx.client.get<Data>("api/morechildren", query);
});

return Promise.all(fetches);
}
}

/** A function to be executed for each element in a Listing. */
export type EachFn<T> = (t: T) => Promise<boolean | void> | boolean | void;

/** A listing of objects. */
export default class Listing<T> {
protected ctx: Context;
protected arr: T[];
protected more?: More<T>;

/**
* Create a new listing.
*
* @internal
*/
constructor(ctx: Context, arr: T[], more?: More<T>) {
this.ctx = ctx;
this.arr = arr;
this.more = more;
}

/**
* Whether or not this listing is empty.
*
* @returns A promise that resolves to true iff the listing is empty.
*/
async empty(): Promise<boolean> {
// If we have elements on hand, it's not empty.
if (this.arr.length > 0) return false;

// If arr is empty and we don't have a way to get more, we're empty.
if (!this.more) return true;

// TODO: Cache this somehow?
const list = await this.more.fetch(this.ctx);
return list.empty();
}

/**
* Whether or not this listing can perform a fetch to get more data.
*
* @returns True iff the listing can fetch more.
*/
canFetchMore(): boolean {
return !!this.more;
}

/**
* Execute a function on each element of the listing.
*
* If the function returns or resolves to `false`, the execution will be
* halted prematurely to allow breaking out in the middle of the iteration.
*
* @param fn The function to execute.
*
* @returns A promise that resolves when the listing has been exausted.
*/
async each(fn: EachFn<T>): Promise<void> {
let page: Listing<T> | null = this;

do {
for (const el of page.arr) {
// If the function returns false at any point, we are done.
const res = await fn(el);
if (res === false) return;
}

if (page.more) {
page = await page.more.fetch(this.ctx);
} else {
page = null;
}
} while (page != null);
}
}

0 comments on commit 90b8f2a

Please sign in to comment.