stack-base-iterator
Version:
Base iterator for values retrieved using a stack of async functions returning values
147 lines (146 loc) • 5.41 kB
JavaScript
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 };