123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- export const SKIP = {};
- const DONE = {
- value: null,
- done: true,
- };
- const RETURN_DONE = {
- // we allow this one to be mutated
- value: null,
- done: true,
- };
- if (!Symbol.asyncIterator) {
- Symbol.asyncIterator = Symbol.for('Symbol.asyncIterator');
- }
- const NO_OPTIONS = {};
- export class RangeIterable {
- constructor(sourceArray) {
- if (sourceArray) {
- this.iterate = sourceArray[Symbol.iterator].bind(sourceArray);
- }
- }
- map(func) {
- let source = this;
- let iterable = new RangeIterable();
- iterable.iterate = (options = NO_OPTIONS) => {
- const { async } = options;
- let iterator =
- source[async ? Symbol.asyncIterator : Symbol.iterator](options);
- if (!async) source.isSync = true;
- let i = -1;
- return {
- next(resolvedResult) {
- let result;
- do {
- let iteratorResult;
- try {
- if (resolvedResult) {
- iteratorResult = resolvedResult;
- resolvedResult = null; // don't go in this branch on next iteration
- } else {
- i++;
- iteratorResult = iterator.next();
- if (iteratorResult.then) {
- if (!async) {
- this.throw(
- new Error(
- 'Can not synchronously iterate with promises as iterator results',
- ),
- );
- }
- return iteratorResult.then(
- (iteratorResult) => this.next(iteratorResult),
- (error) => {
- return this.throw(error);
- },
- );
- }
- }
- if (iteratorResult.done === true) {
- this.done = true;
- if (iterable.onDone) iterable.onDone();
- return iteratorResult;
- }
- try {
- result = func.call(source, iteratorResult.value, i);
- if (result && result.then && async) {
- // if async, wait for promise to resolve before returning iterator result
- return result.then(
- (result) =>
- result === SKIP
- ? this.next()
- : {
- value: result,
- },
- (error) => {
- if (options.continueOnRecoverableError)
- error.continueIteration = true;
- return this.throw(error);
- },
- );
- }
- } catch (error) {
- // if the error came from the user function, we can potentially mark it for continuing iteration
- if (options.continueOnRecoverableError)
- error.continueIteration = true;
- throw error; // throw to next catch to handle
- }
- } catch (error) {
- if (iterable.handleError) {
- // if we have handleError, we can use it to further handle errors
- try {
- result = iterable.handleError(error, i);
- } catch (error2) {
- return this.throw(error2);
- }
- } else return this.throw(error);
- }
- } while (result === SKIP);
- if (result === DONE) {
- return this.return();
- }
- return {
- value: result,
- };
- },
- return(value) {
- if (!this.done) {
- RETURN_DONE.value = value;
- this.done = true;
- if (iterable.onDone) iterable.onDone();
- iterator.return();
- }
- return RETURN_DONE;
- },
- throw(error) {
- if (error.continueIteration) {
- // if it's a recoverable error, we can return or throw without closing the iterator
- if (iterable.returnRecoverableErrors)
- try {
- return {
- value: iterable.returnRecoverableErrors(error),
- };
- } catch (error) {
- // if this throws, we need to go back to closing the iterator
- this.return();
- throw error;
- }
- if (options.continueOnRecoverableError) throw error; // throw without closing iterator
- }
- // else we are done with the iterator (and can throw)
- this.return();
- throw error;
- },
- };
- };
- return iterable;
- }
- [Symbol.asyncIterator](options) {
- if (options) options = { ...options, async: true };
- else options = { async: true };
- return (this.iterator = this.iterate(options));
- }
- [Symbol.iterator](options) {
- return (this.iterator = this.iterate(options));
- }
- filter(func) {
- let iterable = this.map((element) => {
- let result = func(element);
- // handle promise
- if (result?.then)
- return result.then((result) => (result ? element : SKIP));
- else return result ? element : SKIP;
- });
- let iterate = iterable.iterate;
- iterable.iterate = (options = NO_OPTIONS) => {
- // explicitly prevent continue on recoverable error with filter
- if (options.continueOnRecoverableError)
- options = { ...options, continueOnRecoverableError: false };
- return iterate(options);
- };
- return iterable;
- }
- forEach(callback) {
- let iterator = (this.iterator = this.iterate());
- let result;
- while ((result = iterator.next()).done !== true) {
- callback(result.value);
- }
- }
- concat(secondIterable) {
- let concatIterable = new RangeIterable();
- concatIterable.iterate = (options = NO_OPTIONS) => {
- let iterator = (this.iterator = this.iterate(options));
- let isFirst = true;
- function iteratorDone(result) {
- if (isFirst) {
- try {
- isFirst = false;
- iterator =
- secondIterable[
- options.async ? Symbol.asyncIterator : Symbol.iterator
- ]();
- result = iterator.next();
- if (concatIterable.onDone) {
- if (result.then) {
- if (!options.async)
- throw new Error(
- 'Can not synchronously iterate with promises as iterator results',
- );
- result.then(
- (result) => {
- if (result.done()) concatIterable.onDone();
- },
- (error) => {
- this.return();
- throw error;
- },
- );
- } else if (result.done) concatIterable.onDone();
- }
- } catch (error) {
- this.throw(error);
- }
- } else {
- if (concatIterable.onDone) concatIterable.onDone();
- }
- return result;
- }
- return {
- next() {
- try {
- let result = iterator.next();
- if (result.then) {
- if (!options.async)
- throw new Error(
- 'Can not synchronously iterate with promises as iterator results',
- );
- return result.then((result) => {
- if (result.done) return iteratorDone(result);
- return result;
- });
- }
- if (result.done) return iteratorDone(result);
- return result;
- } catch (error) {
- this.throw(error);
- }
- },
- return(value) {
- if (!this.done) {
- RETURN_DONE.value = value;
- this.done = true;
- if (concatIterable.onDone) concatIterable.onDone();
- iterator.return();
- }
- return RETURN_DONE;
- },
- throw(error) {
- if (options.continueOnRecoverableError) throw error;
- this.return();
- throw error;
- },
- };
- };
- return concatIterable;
- }
- flatMap(callback) {
- let mappedIterable = new RangeIterable();
- mappedIterable.iterate = (options = NO_OPTIONS) => {
- let iterator = (this.iterator = this.iterate(options));
- let isFirst = true;
- let currentSubIterator;
- return {
- next(resolvedResult) {
- try {
- do {
- if (currentSubIterator) {
- let result;
- if (resolvedResult) {
- result = resolvedResult;
- resolvedResult = undefined;
- } else result = currentSubIterator.next();
- if (result.then) {
- if (!options.async)
- throw new Error(
- 'Can not synchronously iterate with promises as iterator results',
- );
- return result.then((result) => this.next(result));
- }
- if (!result.done) {
- return result;
- }
- }
- let result;
- if (resolvedResult != undefined) {
- result = resolvedResult;
- resolvedResult = undefined;
- } else result = iterator.next();
- if (result.then) {
- if (!options.async)
- throw new Error(
- 'Can not synchronously iterate with promises as iterator results',
- );
- currentSubIterator = undefined;
- return result.then((result) => this.next(result));
- }
- if (result.done) {
- if (mappedIterable.onDone) mappedIterable.onDone();
- return result;
- }
- try {
- let value = callback(result.value);
- if (value?.then) {
- if (!options.async)
- throw new Error(
- 'Can not synchronously iterate with promises as iterator results',
- );
- return value.then(
- (value) => {
- if (
- Array.isArray(value) ||
- value instanceof RangeIterable
- ) {
- currentSubIterator = value[Symbol.iterator]();
- return this.next();
- } else {
- currentSubIterator = null;
- return { value };
- }
- },
- (error) => {
- if (options.continueOnRecoverableError)
- error.continueIteration = true;
- this.throw(error);
- },
- );
- }
- if (Array.isArray(value) || value instanceof RangeIterable)
- currentSubIterator = value[Symbol.iterator]();
- else {
- currentSubIterator = null;
- return { value };
- }
- } catch (error) {
- if (options.continueOnRecoverableError)
- error.continueIteration = true;
- throw error;
- }
- } while (true);
- } catch (error) {
- this.throw(error);
- }
- },
- return() {
- if (mappedIterable.onDone) mappedIterable.onDone();
- if (currentSubIterator) currentSubIterator.return();
- return iterator.return();
- },
- throw(error) {
- if (options.continueOnRecoverableError) throw error;
- if (mappedIterable.onDone) mappedIterable.onDone();
- if (currentSubIterator) currentSubIterator.return();
- this.return();
- throw error;
- },
- };
- };
- return mappedIterable;
- }
- slice(start, end) {
- let iterable = this.map((element, i) => {
- if (i < start) return SKIP;
- if (i >= end) {
- DONE.value = element;
- return DONE;
- }
- return element;
- });
- iterable.handleError = (error, i) => {
- if (i < start) return SKIP;
- if (i >= end) {
- return DONE;
- }
- throw error;
- };
- return iterable;
- }
- mapError(catch_callback) {
- let iterable = this.map((element) => {
- return element;
- });
- let iterate = iterable.iterate;
- iterable.iterate = (options = NO_OPTIONS) => {
- // we need to ensure the whole stack
- // of iterables is set up to handle recoverable errors and continue iteration
- return iterate({ ...options, continueOnRecoverableError: true });
- };
- iterable.returnRecoverableErrors = catch_callback;
- return iterable;
- }
- next() {
- if (!this.iterator) this.iterator = this.iterate();
- return this.iterator.next();
- }
- toJSON() {
- if (this.asArray && this.asArray.forEach) {
- return this.asArray;
- }
- const error = new Error(
- 'Can not serialize async iterables without first calling resolving asArray',
- );
- error.resolution = this.asArray;
- throw error;
- //return Array.from(this)
- }
- get asArray() {
- if (this._asArray) return this._asArray;
- let promise = new Promise((resolve, reject) => {
- let iterator = this.iterate(true);
- let array = [];
- let iterable = this;
- Object.defineProperty(array, 'iterable', { value: iterable });
- function next(result) {
- while (result.done !== true) {
- if (result.then) {
- return result.then(next);
- } else {
- array.push(result.value);
- }
- result = iterator.next();
- }
- resolve((iterable._asArray = array));
- }
- next(iterator.next());
- });
- promise.iterable = this;
- return this._asArray || (this._asArray = promise);
- }
- resolveData() {
- return this.asArray;
- }
- at(index) {
- for (let entry of this) {
- if (index-- === 0) return entry;
- }
- }
- }
- RangeIterable.prototype.DONE = DONE;
|