UNPKG

writable-consumable-stream

Version:

An async stream which can be iterated over using a for-await-of loop.

150 lines (127 loc) 3.17 kB
class Consumer { constructor(stream, id, startNode, timeout) { this.id = id; this._backpressure = 0; this.currentNode = startNode; this.timeout = timeout; this.isAlive = true; this.stream = stream; this.stream.setConsumer(this.id, this); } getStats() { let stats = { id: this.id, backpressure: this._backpressure }; if (this.timeout != null) { stats.timeout = this.timeout; } return stats; } _resetBackpressure() { this._backpressure = 0; } applyBackpressure(packet) { this._backpressure++; } releaseBackpressure(packet) { this._backpressure--; } getBackpressure() { return this._backpressure; } clearActiveTimeout() { clearTimeout(this._timeoutId); delete this._timeoutId; } write(packet) { if (this._timeoutId !== undefined) { this.clearActiveTimeout(packet); } this.applyBackpressure(packet); if (this._resolve) { this._resolve(); delete this._resolve; } } kill(value) { this._killPacket = {value, done: true}; if (this._timeoutId !== undefined) { this.clearActiveTimeout(this._killPacket); } this._destroy(); if (this._resolve) { this._resolve(); delete this._resolve; } } _destroy() { this.isAlive = false; this._resetBackpressure(); this.stream.removeConsumer(this.id); } async _waitForNextItem(timeout) { return new Promise((resolve, reject) => { this._resolve = resolve; let timeoutId; if (timeout !== undefined) { // Create the error object in the outer scope in order // to get the full stack trace. let error = new Error('Stream consumer iteration timed out'); (async () => { let delay = wait(timeout); timeoutId = delay.timeoutId; await delay.promise; error.name = 'TimeoutError'; delete this._resolve; reject(error); })(); } this._timeoutId = timeoutId; }); } async next() { this.stream.setConsumer(this.id, this); while (true) { if (!this.currentNode.next) { try { await this._waitForNextItem(this.timeout); } catch (error) { this._destroy(); throw error; } } if (this._killPacket) { this._destroy(); let killPacket = this._killPacket; delete this._killPacket; return killPacket; } this.currentNode = this.currentNode.next; this.releaseBackpressure(this.currentNode.data); if (this.currentNode.consumerId && this.currentNode.consumerId !== this.id) { continue; } if (this.currentNode.data.done) { this._destroy(); } return this.currentNode.data; } } return() { delete this.currentNode; this._destroy(); return {}; } [Symbol.asyncIterator]() { return this; } } function wait(timeout) { let timeoutId; let promise = new Promise((resolve) => { timeoutId = setTimeout(resolve, timeout); }); return {timeoutId, promise}; } module.exports = Consumer;