lib/src/util/collection/Iterable.ts

131 lines
3.4 KiB
TypeScript
Raw Normal View History

2021-06-02 01:59:40 +00:00
import {Collection} from './Collection'
import {InjectionAware} from '../../di'
2021-06-02 01:59:40 +00:00
export type MaybeIterationItem<T> = { done: boolean, value?: T }
export type ChunkCallback<T> = (items: Collection<T>) => any
export class StopIteration extends Error {}
/**
* Abstract class representing an iterable, lazy-loaded dataset.
* @abstract
*/
export abstract class Iterable<T> extends InjectionAware {
2021-06-02 01:59:40 +00:00
/**
* The current index of the iterable.
* @type number
*/
protected index = 0
/**
* Get the item of this iterable at the given index, if one exists.
* @param {number} i
* @return Promise<any|undefined>
*/
abstract at(i: number): Promise<T | undefined>
/**
* Get the collection of items in the given range of this iterable.
* @param {number} start
* @param {number} end
* @return Promise<Collection>
*/
abstract range(start: number, end: number): Promise<Collection<T>>
/**
* Count the number of items in this collection.
* @return Promise<number>
*/
abstract count(): Promise<number>
/**
* Get a copy of this iterable.
* @return Iterable
*/
abstract clone(): Iterable<T>
/**
* Return a collection of all items in this iterable.
* @return Promise<Collection>
*/
public async all(): Promise<Collection<T>> {
return this.range(0, (await this.count()) + 1)
}
/**
* Advance to the next value of this iterable.
* @return Promise<MaybeIterationItem>
*/
public async next(): Promise<MaybeIterationItem<T>> {
const i = this.index
if ( i >= await this.count() ) {
return { done: true }
}
this.index = i + 1
2021-06-03 03:36:25 +00:00
return { done: false,
value: await this.at(i) }
2021-06-02 01:59:40 +00:00
}
/**
* Chunk the iterable into the given size and call the callback passing the chunk along.
* @param {number} size
* @param {ChunkCallback} callback
* @return Promise<void>
*/
2021-06-03 03:36:25 +00:00
public async chunk(size: number, callback: ChunkCallback<T>): Promise<void> {
2021-06-02 01:59:40 +00:00
const total = await this.count()
while ( this.index < total ) {
const items = await this.range(this.index, this.index + size - 1)
try {
await callback(items)
} catch ( error ) {
2021-06-03 03:36:25 +00:00
if ( error instanceof StopIteration ) {
break
} else {
throw error
}
2021-06-02 01:59:40 +00:00
}
this.index += size
}
}
/**
* Advance the iterable to the given index.
* @param {number} index
* @return Promise<void>
*/
2021-06-03 03:36:25 +00:00
public async seek(index: number): Promise<void> {
if ( index < 0 ) {
throw new TypeError('Cannot seek to negative index.')
} else if ( index >= await this.count() ) {
throw new TypeError('Cannot seek past last item.')
}
2021-06-02 01:59:40 +00:00
this.index = index
}
/**
* Peek at the next value of the iterable, without advancing.
* @return Promise<any|undefined>
*/
public async peek(): Promise<T | undefined> {
2021-06-03 03:36:25 +00:00
if ( this.index + 1 >= await this.count() ) {
return undefined
} else {
return this.at(this.index + 1)
}
2021-06-02 01:59:40 +00:00
}
/**
* Reset the iterable to the first index.
* @return Promise<any>
*/
2021-06-03 03:36:25 +00:00
public async reset(): Promise<void> {
2021-06-02 01:59:40 +00:00
this.index = 0
}
}