UNPKG

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
"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; }