iterator-helper
Version:
Provide helpers that polyfill all methods defined in [iterator helpers proposal](https://github.com/tc39/proposal-iterator-helpers), both for `Iterator` and `AsyncIterator`, and even more.
558 lines (557 loc) • 19.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HAsyncIterator = void 0;
const types_1 = require("../types");
class HAsyncIterator {
constructor(iterator) {
this[types_1.IteratorSlot] = iterator !== null && iterator !== void 0 ? iterator : this;
}
static from(item) {
if (Symbol.asyncIterator in item) {
return new HAsyncIterator(item[Symbol.asyncIterator]());
}
return new HAsyncIterator(item);
}
next(val) {
return this[types_1.IteratorSlot].next(val);
}
throw(val) {
var _a, _b, _c;
return (_c = (_b = (_a = this[types_1.IteratorSlot]).throw) === null || _b === void 0 ? void 0 : _b.call(_a, val)) !== null && _c !== void 0 ? _c : Promise.resolve({ value: undefined, done: true });
}
return(val) {
var _a, _b, _c;
return (_c = (_b = (_a = this[types_1.IteratorSlot]).return) === null || _b === void 0 ? void 0 : _b.call(_a, val)) !== null && _c !== void 0 ? _c : Promise.resolve({ value: undefined, done: true });
}
/** Map each value of iterator to another value via {callback}. */
map(callback) {
return new HAsyncIterator(HAsyncIterator.map.call(this, callback));
}
static async *map(callback) {
const it = this;
let value = await it.next();
while (!value.done) {
const real_value = await callback(value.value);
const next_value = yield real_value;
value = await it.next(next_value);
}
return value.value;
}
/** Each value is given through {callback}, return `true` if value is needed into returned iterator. */
filter(callback) {
return new HAsyncIterator(HAsyncIterator.filter.call(this, callback));
}
static async *filter(callback) {
const it = this;
let value = await it.next();
let next_value;
while (!value.done) {
const real_value = value.value;
if (await callback(real_value)) {
next_value = yield real_value;
}
value = await it.next(next_value);
}
return value.value;
}
/** Find a specific value that returns `true` in {callback}, and return it. Returns `undefined` otherwise. */
async find(callback) {
const it = this;
let value = await it.next();
while (!value.done) {
const real_value = value.value;
if (await callback(real_value))
return real_value;
value = await it.next();
}
}
/** Return `true` if each value of iterator validate {callback}. */
async every(callback) {
const it = this;
let value = await it.next();
while (!value.done) {
const real_value = value.value;
if (!await callback(real_value))
return false;
value = await it.next();
}
return true;
}
/** Return `true` if one value of iterator validate {callback}. */
async some(callback) {
const it = this;
let value = await it.next();
while (!value.done) {
const real_value = value.value;
if (await callback(real_value))
return true;
value = await it.next();
}
return false;
}
/** Consume iterator and collapse values inside an array. */
async toArray(max_count = Infinity) {
const values = [];
const it = this;
let value = await it.next();
while (!value.done) {
const real_value = value.value;
if (max_count <= 0)
return values;
values.push(real_value);
if (max_count !== Infinity)
max_count--;
value = await it.next();
}
return values;
}
/** Create a new iterator that consume {limit} items, then stops. */
take(limit) {
return new HAsyncIterator(HAsyncIterator.take.call(this, limit));
}
static async *take(limit) {
limit = Number(limit);
if (limit < 0)
throw new RangeError('Invalid limit.');
const it = this;
let value = await it.next();
let next_value;
let remaining = limit;
while (!value.done) {
if (remaining <= 0)
return;
const real_value = value.value;
next_value = yield real_value;
value = await it.next(next_value);
remaining--;
}
return value.value;
}
/** Create a new iterator that skip {limit} items from source iterator, then yield all values. */
drop(limit) {
return new HAsyncIterator(HAsyncIterator.drop.call(this, limit));
}
static async *drop(limit) {
limit = Number(limit);
if (limit < 0)
throw new RangeError('Invalid limit.');
const it = this;
let value = await it.next();
let next_value;
let remaining = limit;
while (!value.done) {
if (remaining > 0) {
remaining--;
value = await it.next(next_value);
continue;
}
const real_value = value.value;
next_value = yield real_value;
value = await it.next(next_value);
remaining--;
}
return value.value;
}
/** Get a pair [index, value] for each remaining value of iterable. */
asIndexedPairs() {
return new HAsyncIterator(HAsyncIterator.asIndexedPairs.call(this));
}
static async *asIndexedPairs() {
let index = 0;
const it = this;
let value = await it.next();
while (!value.done) {
const real_value = value.value;
const next_value = yield [index, real_value];
index++;
value = await it.next(next_value);
}
return value.value;
}
/** Like map, but you can return a new iterator that will be flattened. */
flatMap(mapper) {
return new HAsyncIterator(HAsyncIterator.flatMap.call(this, mapper));
}
static async *flatMap(mapper) {
if (typeof mapper !== 'function') {
throw new TypeError('Mapper must be a function.');
}
const it = this;
let value = await it.next();
let next_value;
while (!value.done) {
const real_value = value.value;
const mapped = await mapper(real_value);
if (Symbol.asyncIterator in mapped) {
// @ts-ignore
yield* mapped[Symbol.asyncIterator]();
}
else if (Symbol.iterator in mapped) {
// @ts-ignore
yield* mapped[Symbol.iterator]();
}
else {
yield mapped;
}
value = await it.next(next_value);
}
return value.value;
}
/** Accumulate each item inside **acc** for each value **value**. */
async reduce(reducer, initial_value) {
let acc = initial_value;
const it = this;
if (acc === undefined) {
acc = (await it.next()).value;
}
for await (const value of it) {
acc = await reducer(acc, value);
}
return acc;
}
/** Iterate over each value of iterator by calling **callback** for each value. */
async forEach(callback) {
const it = this;
let value = await it.next();
while (!value.done) {
const real_value = value.value;
await callback(real_value);
value = await it.next();
}
}
/** Join all the remaining elements of the iterator in a single string with glue {glue}. */
async join(string) {
let final = '';
let first = true;
const it = this;
let value = await it.next();
while (!value.done) {
const real_value = value.value;
if (first) {
first = false;
final += real_value;
}
else {
final += string + real_value;
}
value = await it.next();
}
return final;
}
/** End the iterator and return the number of remaining items. */
async count() {
let count = 0;
const it = this;
let value = await it.next();
while (!value.done) {
count++;
value = await it.next();
}
return count;
}
/** Iterate through current iterator, then through the given iterators in the correct order. */
chain(...iterables) {
return new HAsyncIterator(HAsyncIterator.chain.call(this, ...iterables));
}
static async *chain(...iterables) {
yield* this;
for (const it of iterables) {
if ('next' in it) {
yield* it;
}
else {
// If its not an iterable, make it one
yield* HAsyncIterator.from(it);
}
}
}
zip(...others) {
return new HAsyncIterator(HAsyncIterator.zip.call(this, ...others));
}
static async *zip(...others) {
const it_array = [this, ...others]
.map((e) => Symbol.asyncIterator in e
? e[Symbol.asyncIterator]()
: e);
let values = await Promise.all(it_array.map(e => e.next()));
while (values.every(e => !e.done)) {
yield values.map(e => e.value);
values = await Promise.all(it_array.map(e => e.next()));
}
}
/** Continue iterator until {callback} return a falsy value. */
takeWhile(callback) {
return new HAsyncIterator(HAsyncIterator.takeWhile.call(this, callback));
}
static async *takeWhile(callback) {
const it = this;
let value = await it.next();
let next_value;
while (!value.done) {
const real_value = value.value;
if (await callback(real_value)) {
next_value = yield real_value;
}
else {
return;
}
value = await it.next(next_value);
}
return value.value;
}
/** Skip elements until {callback} return a truthy value. */
dropWhile(callback) {
return new HAsyncIterator(HAsyncIterator.dropWhile.call(this, callback));
}
static async *dropWhile(callback) {
const it = this;
let value = await it.next();
let next_value;
let finished = false;
while (!value.done) {
const real_value = value.value;
if (!finished && await callback(real_value)) {
value = await it.next(next_value);
continue;
}
finished = true;
next_value = yield real_value;
value = await it.next(next_value);
}
return value.value;
}
/** Continue iterator until `null` or `undefined` is encountered. */
fuse() {
return new HAsyncIterator(HAsyncIterator.fuse.call(this));
}
static async *fuse() {
const it = this;
let value = await it.next();
let next_value;
while (!value.done) {
const real_value = value.value;
if (real_value !== undefined && real_value !== null) {
next_value = yield real_value;
}
else {
return;
}
value = await it.next(next_value);
}
return value.value;
}
/** Partition {true} elements to first array, {false} elements to second one. */
async partition(callback) {
const partition1 = [], partition2 = [];
const it = this;
let value = await it.next();
while (!value.done) {
const real_value = value.value;
if (await callback(real_value))
partition1.push(real_value);
else
partition2.push(real_value);
value = await it.next();
}
return [partition1, partition2];
}
/** Group by objects by key according to returned key for each object. */
async groupBy(callback) {
const grouped = {};
const it = this;
let value = await it.next();
while (!value.done) {
const real_value = value.value;
const key = await callback(real_value);
if (key in grouped) {
grouped[key].push(real_value);
}
else {
grouped[key] = [real_value];
}
value = await it.next();
}
return grouped;
}
/** Index this iterator objects in a {Map} with key obtained through {keyGetter}. */
async toIndexedItems(keyGetter) {
const map = new Map();
const it = this;
let value = await it.next();
while (!value.done) {
const realValue = value.value;
const key = await keyGetter(realValue);
map.set(key, realValue);
value = await it.next();
}
return map;
}
/**
* Iterate over items present in both current collection and {otherItems} iterable.
* **Warning**: This is a O(n*m) operation and this will consume {otherItems} iterator/iterable!
*/
intersection(otherItems, isSameItemCallback = Object.is) {
return new HAsyncIterator(HAsyncIterator.intersection.call(this, otherItems, isSameItemCallback));
}
static async *intersection(otherItems, isSameItemCallback = Object.is) {
const otherItemsCollection = await HAsyncIterator.from(otherItems).toArray();
const it = this;
let value = await it.next();
let nextValue;
while (!value.done) {
const realValue = value.value;
// Yield real_value if any {item} match {real_value}
if (await asyncSome(otherItemsCollection, item => isSameItemCallback(realValue, item))) {
nextValue = yield realValue;
}
value = await it.next(nextValue);
}
return value.value;
}
/**
* Iterate over items present only in current collection, not in {otherItems} iterable.
* **Warning**: This is a O(n*m) operation and this will consume {otherItems} iterator/iterable!
*/
difference(otherItems, isSameItemCallback = Object.is) {
return new HAsyncIterator(HAsyncIterator.difference.call(this, otherItems, isSameItemCallback));
}
static async *difference(otherItems, isSameItemCallback = Object.is) {
const it = this;
let value = await it.next();
const otherItemsCollection = await HAsyncIterator.from(otherItems).toArray();
let nextValue;
while (!value.done) {
const realValue = value.value;
// Emit {realValue} only if no value matches in other items
const currentItemPresentInOtherItems = await asyncSome(otherItemsCollection, item => isSameItemCallback(realValue, item));
if (!currentItemPresentInOtherItems) {
nextValue = yield realValue;
}
value = await it.next(nextValue);
}
return value.value;
}
/**
* Iterate over items present only in current collection or only in {otherItems} iterable, but not in both.
* **Warning**: This is a O(n*m) operation and this will consume {otherItems} iterator/iterable!
*/
symmetricDifference(otherItems, isSameItemCallback = Object.is) {
return new HAsyncIterator(HAsyncIterator.symmetricDifference.call(this, otherItems, isSameItemCallback));
}
static async *symmetricDifference(otherItems, isSameItemCallback = Object.is) {
const it = this;
let value = await it.next();
const otherItemsCollection = await HAsyncIterator.from(otherItems).toArray();
const presentInBothCollections = new Set();
let nextValue;
while (!value.done) {
const realValue = value.value;
// Try to find same item as current in {other_items_collection}
const otherItemIndex = await asyncFindIndex(otherItemsCollection, item => isSameItemCallback(realValue, item));
if (otherItemIndex !== -1) {
presentInBothCollections.add(otherItemsCollection[otherItemIndex]);
}
else {
// No match in other collection, can emit it
nextValue = yield realValue;
}
value = await it.next(nextValue);
}
for (const item of otherItemsCollection) {
// Do not emit if {item} is seen in present in both collection items
if (presentInBothCollections.has(item)) {
continue;
}
yield item;
}
return value.value;
}
/** Find the iterator index of the first element that returns a truthy value, -1 otherwise. */
async findIndex(callback) {
const it = this;
let value = await it.next();
let i = 0;
while (!value.done) {
const real_value = value.value;
if (await callback(real_value))
return i;
value = await it.next();
i++;
}
return -1;
}
/** Only works if it is a number iterator. Returns the maximum of iterator. */
async max() {
var _a;
let max = -Infinity;
const it = this;
let value = await it.next();
while (!value.done) {
const real_value = Number((_a = value.value) !== null && _a !== void 0 ? _a : 0);
if (isNaN(real_value)) {
throw new RangeError('Iterator should return numbers only, or null or undefined.');
}
if (max < real_value)
max = real_value;
value = await it.next();
}
return max;
}
/** Only works if it is a number iterator. Returns the minimum of iterator. */
async min() {
var _a;
let min = Infinity;
const it = this;
let value = await it.next();
while (!value.done) {
const real_value = Number((_a = value.value) !== null && _a !== void 0 ? _a : 0);
if (isNaN(real_value)) {
throw new RangeError('Iterator should return numbers only, or null or undefined.');
}
if (min > real_value)
min = real_value;
value = await it.next();
}
return min;
}
/** When iterator ends, go back to the first item then loop. Indefinitively. */
cycle() {
return new HAsyncIterator(HAsyncIterator.cycle.call(this));
}
static async *cycle() {
const values = [];
const it = this;
let value = await it.next();
while (!value.done) {
const real_value = value.value;
values.push(real_value);
const next_value = yield real_value;
value = await it.next(next_value);
}
while (true) {
for (const value of values) {
yield value;
}
}
}
[Symbol.asyncIterator]() {
return this;
}
}
exports.HAsyncIterator = HAsyncIterator;
async function asyncFindIndex(array, finder) {
for (let i = 0; i < array.length; i++) {
if (await finder(array[i])) {
return i;
}
}
return -1;
}
async function asyncSome(array, matcher) {
for (const item of array) {
if (await matcher(item)) {
return true;
}
}
return false;
}