UNPKG

stack-base-iterator

Version:

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

255 lines 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: Object.getOwnPropertyDescriptor(all, name).get }); } _export(exports, { get LinkedList () { return _LinkedListts.default; }, get default () { return StackBaseIterator; } }); var _asynccompat = /*#__PURE__*/ _interop_require_default(require("async-compat")); var _iteratornextcallback = /*#__PURE__*/ _interop_require_default(require("iterator-next-callback")); var _maximizeiterator = require("maximize-iterator"); var _pinkiepromise = /*#__PURE__*/ _interop_require_default(require("pinkie-promise")); var _LinkedListts = /*#__PURE__*/ _interop_require_default(require("./LinkedList.js")); function _class_call_check(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _define_property(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _object_spread(target) { for(var i = 1; i < arguments.length; i++){ var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === "function") { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function(key) { _define_property(target, key, source[key]); }); } return target; } var root = typeof window === 'undefined' ? global : window; // biome-ignore lint/suspicious/noShadowRestrictedNames: Legacy var Symbol = typeof root.Symbol === 'undefined' ? { asyncIterator: undefined } : root.Symbol; var StackBaseIterator = /*#__PURE__*/ function() { "use strict"; function StackBaseIterator() { var options = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}; _class_call_check(this, StackBaseIterator); this.options = _object_spread({}, options); this.options.error = options.error || function defaultError(err) { return !!err; // fail on errors }; this.done = false; this.stack = []; this.queued = []; this.processors = new _LinkedListts.default(); this.processing = new _LinkedListts.default(); this.flushing = false; this.pending = 0; this.endScheduled = false; } var _proto = StackBaseIterator.prototype; _proto.isDone = function isDone() { return this.done; }; _proto.push = function push(fn) { var _this = this; for(var _len = arguments.length, rest = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++){ rest[_key - 1] = arguments[_key]; } if (this.done) return console.log('Attempting to push on a done iterator'); this.stack.push(fn); !rest.length || rest.forEach(function(x) { _this.stack.push(x); }); this._pump(); }; _proto.next = function next() { var _this = this; return new _pinkiepromise.default(function(resolve, reject) { _this._processOrQueue(function(err, result) { err ? reject(err) : resolve(result); }); }); }; _proto[Symbol.asyncIterator] = function() { return this; }; _proto.forEach = function forEach(fn, options, callback) { var _this = this; if (typeof fn !== 'function') throw new Error('Missing each function'); if (typeof options === 'function') { callback = options; options = {}; } if (typeof callback === 'function') { if (this.done) return callback(null, true); options = options || {}; var 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: function() { return !_this.done && _this.stack.length > 0 && _this.queued.length < _this.stack.length; } }; var callbackFired = false; var processor = (0, _maximizeiterator.createProcessor)((0, _iteratornextcallback.default)(this), processorOptions, function(err) { // Guard against double callback (can happen if end() is called while microtask is pending) if (callbackFired) return; callbackFired = true; // Defer completion decision AND processor removal to give deferred work a chance to push // Processor must stay in list so _pump() can signal it to process new items setTimeout(function() { if (!_this.destroyed) _this.processors.remove(processor); processor = null; options = null; var done = !_this.stack.length && _this.pending === 0; if ((err || done) && !_this.done) _this.end(err); callback(err, _this.done || done); }, 0); }); this.processors.push(processor); this._pump(); return; } return new Promise(function(resolve, reject) { return _this.forEach(fn, options, function(err, done) { err ? reject(err) : resolve(done); }); }); }; _proto.end = function 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(); }; _proto.destroy = function destroy(err) { if (this.destroyed) throw new Error('Already destroyed'); this.destroyed = true; this.end(err); }; _proto._pump = function _pump() { // Flush loop pattern: if already flushing, the outer loop will handle new work // This prevents stack overflow by avoiding recursion entirely if (this.flushing) return; this.flushing = true; 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 } this.flushing = false; }; _proto._scheduleEndCheck = function _scheduleEndCheck() { var _this = this; // Defer end check to give other deferred work a chance to push if (this.endScheduled || this.done) return; this.endScheduled = true; setTimeout(function() { _this.endScheduled = false; // Re-check ALL conditions after deferral if (_this.stack.length === 0 && _this.processing.length === 0 && _this.pending === 0 && !_this.done) { _this.end(); } }, 0); }; _proto._processOrQueue = function _processOrQueue(callback) { var _this = this; if (this.done) return callback(null, { done: true, value: null }); // nothing to process so queue if (this.stack.length === 0) { this.queued.push(callback); return; } // process next var next = this.stack.pop(); this.processing.push(callback); this.pending++; var callbackFired = false; next(this, function(err, result) { // Guard against callback being called multiple times (buggy iterators) if (callbackFired) { console.warn('stack-base-iterator: callback called multiple times - this indicates a bug in the iterator implementation'); return; } callbackFired = true; _this.pending--; _this.processing.remove(callback); // done if (_this.done) return callback(null, { done: true, value: null }); // skip error if (err && _asynccompat.default.defaultValue(_this.options.error(err), true)) err = null; // handle callback if (err) callback(err); else if (!result) { _this.queued.push(callback); setTimeout(function() { return _this._pump(); }, 0); // Deferred to start new call stack } else callback(null, result); // Only schedule end check when we might actually be done // This prevents premature end checks from earlier items if (_this.stack.length === 0 && _this.processing.length === 0 && _this.pending === 0) { _this._scheduleEndCheck(); } }); }; return StackBaseIterator; }(); /* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }