UNPKG

stack-base-iterator

Version:

Base iterator for values retrieved using a stack of async functions returning values

147 lines (146 loc) 5.41 kB
import compat from 'async-compat'; import nextCallback from 'iterator-next-callback'; import { createProcessor } from 'maximize-iterator'; import Pinkie from 'pinkie-promise'; import LinkedList from './LinkedList.js'; // biome-ignore lint/suspicious/noShadowRestrictedNames: Legacy const Symbol = typeof global.Symbol === 'undefined' ? { asyncIterator: '@@' } : global.Symbol; export { default as LinkedList } from './LinkedList.js'; let StackBaseIterator = class StackBaseIterator { isDone() { return this.done; } push(fn, ...rest) { if (this.done) return console.log('Attempting to push on a done iterator'); this.stack.push(fn); !rest.length || rest.forEach((x)=>this.stack.push(x)); this._pump(); } next() { return new Pinkie((resolve, reject)=>{ this._processOrQueue((err, result)=>{ err ? reject(err) : resolve(result); }); }); } [Symbol.asyncIterator]() { return this; } forEach(fn, options, callback) { if (typeof fn !== 'function') throw new Error('Missing each function'); if (typeof options === 'function') { callback = options; options = {}; } if (typeof callback === 'function') { if (this.done) { callback(null, true); return; } options = options || {}; const processorOptions = { each: fn, callbacks: options.callbacks || false, concurrency: options.concurrency || 1, limit: options.limit || Infinity, error: options.error || function defaultError() { return true; // default is exit on error }, total: 0, counter: 0, canProcess: ()=>{ return !this.done && this.stack.length > 0 && this.queued.length < this.stack.length; } }; let processor = createProcessor(nextCallback(this), processorOptions, (err)=>{ if (!this.destroyed) this.processors.remove(processor); processor = null; options = null; const done = !this.stack.length; if ((err || done) && !this.done) this.end(err); return callback(err, this.done || done); }); this.processors.push(processor); this._pump(); return; } return new Promise((resolve, reject)=>this.forEach(fn, options, (err, done)=>{ err ? reject(err) : resolve(done); })); } end(err) { if (this.done) return; this.done = true; while(this.processors.length > 0)this.processors.pop()(err || true); while(this.processing.length > 0)err ? this.processing.pop()(err) : this.processing.pop()(null, { done: true, value: null }); while(this.queued.length > 0)err ? this.queued.pop()(err) : this.queued.pop()(null, { done: true, value: null }); while(this.stack.length > 0)this.stack.pop(); } destroy(err) { if (this.destroyed) throw new Error('Already destroyed'); this.destroyed = true; this.end(err); } _pump() { if (!this.done && this.processors.length > 0 && this.stack.length > 0 && this.stack.length > this.queued.length) this.processors.last()(false); // try to queue more while(this.stack.length > 0 && this.queued.length > 0){ this._processOrQueue(this.queued.pop()); if (!this.done && this.processors.length > 0 && this.stack.length > 0 && this.stack.length > this.queued.length) this.processors.last()(false); // try to queue more } } _processOrQueue(callback) { if (this.done) { callback(null, { done: true, value: null }); return; } // nothing to process so queue if (this.stack.length === 0) { this.queued.push(callback); return; } // process next const next = this.stack.pop(); this.processing.push(callback); next(this, (err, result)=>{ this.processing.remove(callback); // done if (this.done) return callback(null, { done: true, value: null }); // skip error if (err && compat.defaultValue(this.options.error(err), true)) err = null; // handle callback if (err) callback(err); else if (!result) this._processOrQueue(callback); else callback(null, result); // done if (this.stack.length === 0 && this.processing.length === 0 && !this.done) this.end(); // end }); } constructor(options = {}){ this.options = { ...options }; this.options.error = options.error || function defaultError(err) { return !!err; // fail on errors }; this.done = false; this.stack = []; this.queued = []; this.processors = new LinkedList(); this.processing = new LinkedList(); } }; export { StackBaseIterator as default };