async-stream-reader
Version:
A stream reader with async functions
156 lines (131 loc) • 4.54 kB
text/typescript
'use strict';
import * as assert from 'assert';
import * as Debug from 'debug';
import * as Stream from 'stream';
import * as EventEmitter from 'events';
const debug = Debug('async-stream-reader');
const DATA = Symbol('READER DATA');
const END = Symbol('READER END');
const ERROR = Symbol('READER ERROR');
type Readable = Stream.Readable | EventEmitter;
type Options = {
pause?: string,
resume?: string,
isPaused?: string,
end?: string,
events?: {
data?: string | string[],
end?: string | string[],
error?: string | string[],
}
}
type Event = {
type: symbol,
args: any[]
}
function isFunction(test: any): test is Function {
return test && test instanceof Function;
}
class Reader {
private readable: Readable;
private eventHolder: Event[];
private _pause: string;
private _resume: string;
private _isPaused: string;
private _endArgument: any;
private _emitter: EventEmitter;
static get ERROR(): symbol {
return ERROR;
}
static get END(): symbol {
return END;
}
constructor(readable: Readable, options: Options = {}) {
debug('construct');
assert(readable instanceof Stream.Readable || readable instanceof EventEmitter,
'Only support Stream.Readable or EventEmitter');
this.readable = readable;
this.eventHolder = [];
this._pause = options.pause || 'pause';
this._resume = options.resume || 'resume';
this._isPaused = options.isPaused || 'isPaused';
this._endArgument = options.end || undefined;
this._emitter = new EventEmitter();
const events = options.events || {};
let dataEvents = events.data || 'data';
let endEvents = events.end || 'end';
let errorEvents = events.error || 'error';
dataEvents = Array.isArray(dataEvents) ? dataEvents : [dataEvents];
endEvents = Array.isArray(endEvents) ? endEvents : [endEvents];
errorEvents = Array.isArray(errorEvents) ? errorEvents : [errorEvents];
this.pause();
for (const name of [...dataEvents, ...endEvents, ...errorEvents]) {
this.readable.on(name, (...args: any[]) => this._emitter.emit('any', {name, args}));
}
for (const name of dataEvents) {
this.readable.on(name, (...args: any[]) => this.eventHolder.push({type: DATA, args}));
}
for (const name of endEvents) {
this.readable.on(name, (...args: any[]) => this.eventHolder.push({type: END, args}));
}
for (const name of errorEvents) {
this.readable.on(name, (...args: any[]) => this.eventHolder.push({type: ERROR, args}));
}
}
private isReadableMethod(methodName: string): boolean {
return isFunction((this.readable as any)[methodName]);
}
private callReadableMethod<T = void>(methodName: string): T {
return ((this.readable as any)[methodName] as () => T)();
}
pause(): Reader {
if (this._isPaused && this.isReadableMethod(this._isPaused) &&
this.callReadableMethod<boolean>(this._isPaused)) {
return this;
}
if (this.isReadableMethod(this._pause)) {
debug('pause');
this.callReadableMethod(this._pause);
}
return this;
}
resume(): Reader {
if (this._isPaused && this.isReadableMethod(this._isPaused) &&
!this.callReadableMethod<boolean>(this._isPaused)) {
return this;
}
if (this.isReadableMethod(this._resume)) {
debug('resume');
this.callReadableMethod(this._resume)
}
return this;
}
_emitted(): Promise<any> {
return new Promise(resolve => this._emitter.once('any', resolve));
}
async nextEvent(): Promise<Event> {
debug('next event');
if (this.eventHolder.length > 0) {
this.pause();
return this.eventHolder.shift()!;
}
this.resume();
await this._emitted();
return this.nextEvent();
}
async next(): Promise<any> {
const {args, type} = await this.nextEvent();
debug('next', args, type);
const result = args[0];
if (type === Reader.ERROR) {
debug('error');
throw result;
}
if (type === Reader.END) {
debug('end');
return this._endArgument;
}
return result;
}
}
module.exports = Reader;